[{"id":"/root","type":"ROOT","loc":{"start":0,"end":70982,"column":{"s":0,"e":146},"line":{"s":0,"e":1139}},"dim":[""],"code":null},{"id":"/root/children/0","type":"paragraph","loc":{"start":0,"end":77,"line":{"s":1,"e":1,"code":[";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }"]},"column":{"s":0,"e":77}},"dim":["","paragraph.0"],"code":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }"},{"id":"/root/children/0/children/0","type":"text","loc":{"start":0,"end":77,"line":{"s":1,"e":1,"code":[";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }"]},"column":{"s":0,"e":77}},"dim":["","paragraph.0","text.0"],"code":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }"},{"id":"/root/children/1","type":"heading","loc":{"start":79,"end":111,"line":{"s":3,"e":3,"code":["# player.js - Analysis & Summary"]},"column":{"s":0,"e":32}},"dim":["","heading.1"],"code":"# player.js - Analysis & Summary","symbName":"heading","symbRange":[113,70981],"symbRangeL":[3,4],"outerCode":"","outerHtml":""},{"id":"/root/children/1/children/0","type":"text","loc":{"start":81,"end":111,"line":{"s":3,"e":3,"code":["# player.js - Analysis & Summary"]},"column":{"s":2,"e":32}},"dim":["","heading.1","text.0"],"code":"player.js - Analysis & Summary"},{"id":"/root/children/2","type":"heading","loc":{"start":113,"end":124,"line":{"s":5,"e":5,"code":["## Overview"]},"column":{"s":0,"e":11}},"dim":["","heading.2"],"code":"## Overview","symbName":"heading","symbRange":[126,468],"symbRangeL":[5,10],"outerCode":"\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.","outerHtml":"\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>"},{"id":"/root/children/2/children/0","type":"text","loc":{"start":116,"end":124,"line":{"s":5,"e":5,"code":["## Overview"]},"column":{"s":3,"e":11}},"dim":["","heading.2","text.0"],"code":"Overview"},{"id":"/root/children/3","type":"paragraph","loc":{"start":126,"end":380,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":0,"e":254}},"dim":["","paragraph.3"],"code":"`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."},{"id":"/root/children/3/children/0","type":"inlineCode","loc":{"start":126,"end":137,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":0,"e":11}},"dim":["","paragraph.3","inlineCode.0"],"code":"`player.js`"},{"id":"/root/children/3/children/1","type":"text","loc":{"start":137,"end":170,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":11,"e":44}},"dim":["","paragraph.3","text.1"],"code":" is the main orchestrator of the "},{"id":"/root/children/3/children/2","type":"strong","loc":{"start":170,"end":192,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":44,"e":66}},"dim":["","paragraph.3","strong.2"],"code":"**repolero-prototype**"},{"id":"/root/children/3/children/2/children/0","type":"text","loc":{"start":172,"end":190,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":46,"e":64}},"dim":["","paragraph.3","strong.2","text.0"],"code":"repolero-prototype"},{"id":"/root/children/3/children/3","type":"text","loc":{"start":192,"end":289,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":66,"e":163}},"dim":["","paragraph.3","text.3"],"code":" app (~3600 lines). It is a single-page application with hash-based routing, four visual layers ("},{"id":"/root/children/3/children/4","type":"strong","loc":{"start":289,"end":298,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":163,"e":172}},"dim":["","paragraph.3","strong.4"],"code":"**glass**"},{"id":"/root/children/3/children/4/children/0","type":"text","loc":{"start":291,"end":296,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":165,"e":170}},"dim":["","paragraph.3","strong.4","text.0"],"code":"glass"},{"id":"/root/children/3/children/5","type":"text","loc":{"start":298,"end":300,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":172,"e":174}},"dim":["","paragraph.3","text.5"],"code":", "},{"id":"/root/children/3/children/6","type":"strong","loc":{"start":300,"end":309,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":174,"e":183}},"dim":["","paragraph.3","strong.6"],"code":"**paper**"},{"id":"/root/children/3/children/6/children/0","type":"text","loc":{"start":302,"end":307,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":176,"e":181}},"dim":["","paragraph.3","strong.6","text.0"],"code":"paper"},{"id":"/root/children/3/children/7","type":"text","loc":{"start":309,"end":311,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":183,"e":185}},"dim":["","paragraph.3","text.7"],"code":", "},{"id":"/root/children/3/children/8","type":"strong","loc":{"start":311,"end":320,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":185,"e":194}},"dim":["","paragraph.3","strong.8"],"code":"**space**"},{"id":"/root/children/3/children/8/children/0","type":"text","loc":{"start":313,"end":318,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":187,"e":192}},"dim":["","paragraph.3","strong.8","text.0"],"code":"space"},{"id":"/root/children/3/children/9","type":"text","loc":{"start":320,"end":322,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":194,"e":196}},"dim":["","paragraph.3","text.9"],"code":", "},{"id":"/root/children/3/children/10","type":"strong","loc":{"start":322,"end":330,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":196,"e":204}},"dim":["","paragraph.3","strong.10"],"code":"**edit**"},{"id":"/root/children/3/children/10/children/0","type":"text","loc":{"start":324,"end":328,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":198,"e":202}},"dim":["","paragraph.3","strong.10","text.0"],"code":"edit"},{"id":"/root/children/3/children/11","type":"text","loc":{"start":330,"end":380,"line":{"s":7,"e":7,"code":["`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ."]},"column":{"s":204,"e":254}},"dim":["","paragraph.3","text.11"],"code":"), and real-time collaboration via STOMP/RabbitMQ."},{"id":"/root/children/4","type":"paragraph","loc":{"start":382,"end":468,"line":{"s":9,"e":9,"code":["The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive."]},"column":{"s":0,"e":86}},"dim":["","paragraph.4"],"code":"The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive."},{"id":"/root/children/4/children/0","type":"text","loc":{"start":382,"end":398,"line":{"s":9,"e":9,"code":["The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive."]},"column":{"s":0,"e":16}},"dim":["","paragraph.4","text.0"],"code":"The HTML shell ("},{"id":"/root/children/4/children/1","type":"inlineCode","loc":{"start":398,"end":412,"line":{"s":9,"e":9,"code":["The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive."]},"column":{"s":16,"e":30}},"dim":["","paragraph.4","inlineCode.1"],"code":"`view.jq.html`"},{"id":"/root/children/4/children/2","type":"text","loc":{"start":412,"end":440,"line":{"s":9,"e":9,"code":["The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive."]},"column":{"s":30,"e":58}},"dim":["","paragraph.4","text.2"],"code":") defines the DOM skeleton. "},{"id":"/root/children/4/children/3","type":"inlineCode","loc":{"start":440,"end":451,"line":{"s":9,"e":9,"code":["The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive."]},"column":{"s":58,"e":69}},"dim":["","paragraph.4","inlineCode.3"],"code":"`player.js`"},{"id":"/root/children/4/children/4","type":"text","loc":{"start":451,"end":468,"line":{"s":9,"e":9,"code":["The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive."]},"column":{"s":69,"e":86}},"dim":["","paragraph.4","text.4"],"code":" brings it alive."},{"id":"/root/children/5","type":"heading","loc":{"start":470,"end":534,"line":{"s":11,"e":11,"code":["## repolero-prototype — UI concept (from repolero-prototype.mdd)"]},"column":{"s":0,"e":64}},"dim":["","heading.5"],"code":"## repolero-prototype — UI concept (from repolero-prototype.mdd)","symbName":"heading","symbRange":[536,70981],"symbRangeL":[11,12],"outerCode":"","outerHtml":""},{"id":"/root/children/5/children/0","type":"text","loc":{"start":473,"end":534,"line":{"s":11,"e":11,"code":["## repolero-prototype — UI concept (from repolero-prototype.mdd)"]},"column":{"s":3,"e":64}},"dim":["","heading.5","text.0"],"code":"repolero-prototype — UI concept (from repolero-prototype.mdd)"},{"id":"/root/children/6","type":"heading","loc":{"start":536,"end":557,"line":{"s":13,"e":13,"code":["### Design principles"]},"column":{"s":0,"e":21}},"dim":["","heading.6"],"code":"### Design principles","symbName":"heading","symbRange":[559,759],"symbRangeL":[13,18],"outerCode":"\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE","outerHtml":"\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>"},{"id":"/root/children/6/children/0","type":"text","loc":{"start":540,"end":557,"line":{"s":13,"e":13,"code":["### Design principles"]},"column":{"s":4,"e":21}},"dim":["","heading.6","text.0"],"code":"Design principles"},{"id":"/root/children/7","type":"list","loc":{"start":559,"end":759,"line":{"s":15,"e":17,"code":["- **Minimal layout** (water.css-inspired)","- **Mouse-less interaction** — keyboard-first","- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"]},"column":{"s":0,"e":112}},"dim":["","list.7"],"code":"- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE","symbName":"list","symbRange":[761,1518],"symbRangeL":[15,33],"outerCode":"- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization","outerHtml":"<ul><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>"},{"id":"/root/children/7/children/0","type":"listItem","loc":{"start":559,"end":600,"line":{"s":15,"e":15,"code":["- **Minimal layout** (water.css-inspired)"]},"column":{"s":0,"e":41}},"dim":["","list.7","listItem.0"],"code":"- **Minimal layout** (water.css-inspired)"},{"id":"/root/children/7/children/0/children/0","type":"paragraph","loc":{"start":561,"end":600,"line":{"s":15,"e":15,"code":["- **Minimal layout** (water.css-inspired)"]},"column":{"s":2,"e":41}},"dim":["","list.7","listItem.0","paragraph.0"],"code":"**Minimal layout** (water.css-inspired)"},{"id":"/root/children/7/children/0/children/0/children/0","type":"strong","loc":{"start":561,"end":579,"line":{"s":15,"e":15,"code":["- **Minimal layout** (water.css-inspired)"]},"column":{"s":2,"e":20}},"dim":["","list.7","listItem.0","paragraph.0","strong.0"],"code":"**Minimal layout**"},{"id":"/root/children/7/children/0/children/0/children/0/children/0","type":"text","loc":{"start":563,"end":577,"line":{"s":15,"e":15,"code":["- **Minimal layout** (water.css-inspired)"]},"column":{"s":4,"e":18}},"dim":["","list.7","listItem.0","paragraph.0","strong.0","text.0"],"code":"Minimal layout"},{"id":"/root/children/7/children/0/children/0/children/1","type":"text","loc":{"start":579,"end":600,"line":{"s":15,"e":15,"code":["- **Minimal layout** (water.css-inspired)"]},"column":{"s":20,"e":41}},"dim":["","list.7","listItem.0","paragraph.0","text.1"],"code":" (water.css-inspired)"},{"id":"/root/children/7/children/1","type":"listItem","loc":{"start":601,"end":646,"line":{"s":16,"e":16,"code":["- **Mouse-less interaction** — keyboard-first"]},"column":{"s":0,"e":45}},"dim":["","list.7","listItem.1"],"code":"- **Mouse-less interaction** — keyboard-first"},{"id":"/root/children/7/children/1/children/0","type":"paragraph","loc":{"start":603,"end":646,"line":{"s":16,"e":16,"code":["- **Mouse-less interaction** — keyboard-first"]},"column":{"s":2,"e":45}},"dim":["","list.7","listItem.1","paragraph.0"],"code":"**Mouse-less interaction** — keyboard-first"},{"id":"/root/children/7/children/1/children/0/children/0","type":"strong","loc":{"start":603,"end":629,"line":{"s":16,"e":16,"code":["- **Mouse-less interaction** — keyboard-first"]},"column":{"s":2,"e":28}},"dim":["","list.7","listItem.1","paragraph.0","strong.0"],"code":"**Mouse-less interaction**"},{"id":"/root/children/7/children/1/children/0/children/0/children/0","type":"text","loc":{"start":605,"end":627,"line":{"s":16,"e":16,"code":["- **Mouse-less interaction** — keyboard-first"]},"column":{"s":4,"e":26}},"dim":["","list.7","listItem.1","paragraph.0","strong.0","text.0"],"code":"Mouse-less interaction"},{"id":"/root/children/7/children/1/children/0/children/1","type":"text","loc":{"start":629,"end":646,"line":{"s":16,"e":16,"code":["- **Mouse-less interaction** — keyboard-first"]},"column":{"s":28,"e":45}},"dim":["","list.7","listItem.1","paragraph.0","text.1"],"code":" — keyboard-first"},{"id":"/root/children/7/children/2","type":"listItem","loc":{"start":647,"end":759,"line":{"s":17,"e":17,"code":["- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"]},"column":{"s":0,"e":112}},"dim":["","list.7","listItem.2"],"code":"- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"},{"id":"/root/children/7/children/2/children/0","type":"paragraph","loc":{"start":649,"end":759,"line":{"s":17,"e":17,"code":["- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"]},"column":{"s":2,"e":112}},"dim":["","list.7","listItem.2","paragraph.0"],"code":"**Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"},{"id":"/root/children/7/children/2/children/0/children/0","type":"strong","loc":{"start":649,"end":708,"line":{"s":17,"e":17,"code":["- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"]},"column":{"s":2,"e":61}},"dim":["","list.7","listItem.2","paragraph.0","strong.0"],"code":"**Keyboard shortcuts follow key ergonomics, not semantics**"},{"id":"/root/children/7/children/2/children/0/children/0/children/0","type":"text","loc":{"start":651,"end":706,"line":{"s":17,"e":17,"code":["- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"]},"column":{"s":4,"e":59}},"dim":["","list.7","listItem.2","paragraph.0","strong.0","text.0"],"code":"Keyboard shortcuts follow key ergonomics, not semantics"},{"id":"/root/children/7/children/2/children/0/children/1","type":"text","loc":{"start":708,"end":759,"line":{"s":17,"e":17,"code":["- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"]},"column":{"s":61,"e":112}},"dim":["","list.7","listItem.2","paragraph.0","text.1"],"code":" — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE"},{"id":"/root/children/8","type":"heading","loc":{"start":761,"end":777,"line":{"s":19,"e":19,"code":["### Three layers"]},"column":{"s":0,"e":16}},"dim":["","heading.8"],"code":"### Three layers","symbName":"heading","symbRange":[779,1390],"symbRangeL":[19,27],"outerCode":"\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |","outerHtml":"\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>"},{"id":"/root/children/8/children/0","type":"text","loc":{"start":765,"end":777,"line":{"s":19,"e":19,"code":["### Three layers"]},"column":{"s":4,"e":16}},"dim":["","heading.8","text.0"],"code":"Three layers"},{"id":"/root/children/9","type":"paragraph","loc":{"start":779,"end":1390,"line":{"s":21,"e":26,"code":["| Layer      | Purpose                             | Implementation                                 |","| ---------- | ----------------------------------- | ---------------------------------------------- |","| **space**  | Map view                            | player/map-show.js (OpenLayers)                |","| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |","| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |","| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |"]},"column":{"s":0,"e":101}},"dim":["","paragraph.9"],"code":"| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |"},{"id":"/root/children/9/children/0","type":"text","loc":{"start":779,"end":985,"line":{"s":21,"e":23,"code":["| Layer      | Purpose                             | Implementation                                 |","| ---------- | ----------------------------------- | ---------------------------------------------- |","| **space**  | Map view                            | player/map-show.js (OpenLayers)                |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.9","text.0"],"code":"| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| "},{"id":"/root/children/9/children/1","type":"strong","loc":{"start":985,"end":994,"line":{"s":23,"e":23,"code":["| **space**  | Map view                            | player/map-show.js (OpenLayers)                |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.9","strong.1"],"code":"**space**"},{"id":"/root/children/9/children/1/children/0","type":"text","loc":{"start":987,"end":992,"line":{"s":23,"e":23,"code":["| **space**  | Map view                            | player/map-show.js (OpenLayers)                |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.9","strong.1","text.0"],"code":"space"},{"id":"/root/children/9/children/2","type":"text","loc":{"start":994,"end":1087,"line":{"s":23,"e":24,"code":["| **space**  | Map view                            | player/map-show.js (OpenLayers)                |","| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |"]},"column":{"s":11,"e":2}},"dim":["","paragraph.9","text.2"],"code":"  | Map view                            | player/map-show.js (OpenLayers)                |\n| "},{"id":"/root/children/9/children/3","type":"strong","loc":{"start":1087,"end":1096,"line":{"s":24,"e":24,"code":["| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.9","strong.3"],"code":"**paper**"},{"id":"/root/children/9/children/3/children/0","type":"text","loc":{"start":1089,"end":1094,"line":{"s":24,"e":24,"code":["| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.9","strong.3","text.0"],"code":"paper"},{"id":"/root/children/9/children/4","type":"text","loc":{"start":1096,"end":1189,"line":{"s":24,"e":25,"code":["| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |","| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |"]},"column":{"s":11,"e":2}},"dim":["","paragraph.9","text.4"],"code":"  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| "},{"id":"/root/children/9/children/5","type":"strong","loc":{"start":1189,"end":1199,"line":{"s":25,"e":25,"code":["| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.9","strong.5"],"code":"**editor**"},{"id":"/root/children/9/children/5/children/0","type":"text","loc":{"start":1191,"end":1197,"line":{"s":25,"e":25,"code":["| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |"]},"column":{"s":4,"e":10}},"dim":["","paragraph.9","strong.5","text.0"],"code":"editor"},{"id":"/root/children/9/children/6","type":"text","loc":{"start":1199,"end":1291,"line":{"s":25,"e":26,"code":["| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |","| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |"]},"column":{"s":12,"e":2}},"dim":["","paragraph.9","text.6"],"code":" | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| "},{"id":"/root/children/9/children/7","type":"strong","loc":{"start":1291,"end":1300,"line":{"s":26,"e":26,"code":["| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.9","strong.7"],"code":"**glass**"},{"id":"/root/children/9/children/7/children/0","type":"text","loc":{"start":1293,"end":1298,"line":{"s":26,"e":26,"code":["| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.9","strong.7","text.0"],"code":"glass"},{"id":"/root/children/9/children/8","type":"text","loc":{"start":1300,"end":1390,"line":{"s":26,"e":26,"code":["| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |"]},"column":{"s":11,"e":101}},"dim":["","paragraph.9","text.8"],"code":"  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |"},{"id":"/root/children/10","type":"heading","loc":{"start":1392,"end":1415,"line":{"s":28,"e":28,"code":["### Glass menu selector"]},"column":{"s":0,"e":23}},"dim":["","heading.10"],"code":"### Glass menu selector","symbName":"heading","symbRange":[1417,1499],"symbRangeL":[28,31],"outerCode":"\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete","outerHtml":"\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>"},{"id":"/root/children/10/children/0","type":"text","loc":{"start":1396,"end":1415,"line":{"s":28,"e":28,"code":["### Glass menu selector"]},"column":{"s":4,"e":23}},"dim":["","heading.10","text.0"],"code":"Glass menu selector"},{"id":"/root/children/11","type":"paragraph","loc":{"start":1417,"end":1499,"line":{"s":30,"e":30,"code":["Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete"]},"column":{"s":0,"e":82}},"dim":["","paragraph.11"],"code":"Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete"},{"id":"/root/children/11/children/0","type":"text","loc":{"start":1417,"end":1499,"line":{"s":30,"e":30,"code":["Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete"]},"column":{"s":0,"e":82}},"dim":["","paragraph.11","text.0"],"code":"Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete"},{"id":"/root/children/12","type":"heading","loc":{"start":1501,"end":1518,"line":{"s":32,"e":32,"code":["### Visualization"]},"column":{"s":0,"e":17}},"dim":["","heading.12"],"code":"### Visualization","symbName":"heading","symbRange":[1520,1662],"symbRangeL":[32,36],"outerCode":"\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)","outerHtml":"\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>"},{"id":"/root/children/12/children/0","type":"text","loc":{"start":1505,"end":1518,"line":{"s":32,"e":32,"code":["### Visualization"]},"column":{"s":4,"e":17}},"dim":["","heading.12","text.0"],"code":"Visualization"},{"id":"/root/children/13","type":"list","loc":{"start":1520,"end":1662,"line":{"s":34,"e":35,"code":["- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap","- **D3**: hierarchy pack, message sequence chart (rc page)"]},"column":{"s":0,"e":58}},"dim":["","list.13"],"code":"- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)","symbName":"list","symbRange":[1664,1681],"symbRangeL":[34,38],"outerCode":"- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics","outerHtml":"<ul><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>"},{"id":"/root/children/13/children/0","type":"listItem","loc":{"start":1520,"end":1603,"line":{"s":34,"e":34,"code":["- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap"]},"column":{"s":0,"e":83}},"dim":["","list.13","listItem.0"],"code":"- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap"},{"id":"/root/children/13/children/0/children/0","type":"paragraph","loc":{"start":1522,"end":1603,"line":{"s":34,"e":34,"code":["- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap"]},"column":{"s":2,"e":83}},"dim":["","list.13","listItem.0","paragraph.0"],"code":"**Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap"},{"id":"/root/children/13/children/0/children/0/children/0","type":"strong","loc":{"start":1522,"end":1535,"line":{"s":34,"e":34,"code":["- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap"]},"column":{"s":2,"e":15}},"dim":["","list.13","listItem.0","paragraph.0","strong.0"],"code":"**Vega-Lite**"},{"id":"/root/children/13/children/0/children/0/children/0/children/0","type":"text","loc":{"start":1524,"end":1533,"line":{"s":34,"e":34,"code":["- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap"]},"column":{"s":4,"e":13}},"dim":["","list.13","listItem.0","paragraph.0","strong.0","text.0"],"code":"Vega-Lite"},{"id":"/root/children/13/children/0/children/0/children/1","type":"text","loc":{"start":1535,"end":1603,"line":{"s":34,"e":34,"code":["- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap"]},"column":{"s":15,"e":83}},"dim":["","list.13","listItem.0","paragraph.0","text.1"],"code":": parallel coords, word cloud, radar, radial tree, calendar, heatmap"},{"id":"/root/children/13/children/1","type":"listItem","loc":{"start":1604,"end":1662,"line":{"s":35,"e":35,"code":["- **D3**: hierarchy pack, message sequence chart (rc page)"]},"column":{"s":0,"e":58}},"dim":["","list.13","listItem.1"],"code":"- **D3**: hierarchy pack, message sequence chart (rc page)"},{"id":"/root/children/13/children/1/children/0","type":"paragraph","loc":{"start":1606,"end":1662,"line":{"s":35,"e":35,"code":["- **D3**: hierarchy pack, message sequence chart (rc page)"]},"column":{"s":2,"e":58}},"dim":["","list.13","listItem.1","paragraph.0"],"code":"**D3**: hierarchy pack, message sequence chart (rc page)"},{"id":"/root/children/13/children/1/children/0/children/0","type":"strong","loc":{"start":1606,"end":1612,"line":{"s":35,"e":35,"code":["- **D3**: hierarchy pack, message sequence chart (rc page)"]},"column":{"s":2,"e":8}},"dim":["","list.13","listItem.1","paragraph.0","strong.0"],"code":"**D3**"},{"id":"/root/children/13/children/1/children/0/children/0/children/0","type":"text","loc":{"start":1608,"end":1610,"line":{"s":35,"e":35,"code":["- **D3**: hierarchy pack, message sequence chart (rc page)"]},"column":{"s":4,"e":6}},"dim":["","list.13","listItem.1","paragraph.0","strong.0","text.0"],"code":"D3"},{"id":"/root/children/13/children/1/children/0/children/1","type":"text","loc":{"start":1612,"end":1662,"line":{"s":35,"e":35,"code":["- **D3**: hierarchy pack, message sequence chart (rc page)"]},"column":{"s":8,"e":58}},"dim":["","list.13","listItem.1","paragraph.0","text.1"],"code":": hierarchy pack, message sequence chart (rc page)"},{"id":"/root/children/14","type":"heading","loc":{"start":1664,"end":1681,"line":{"s":37,"e":37,"code":["### Data dynamics"]},"column":{"s":0,"e":17}},"dim":["","heading.14"],"code":"### Data dynamics","symbName":"heading","symbRange":[1683,1831],"symbRangeL":[37,42],"outerCode":"\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)","outerHtml":"\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>"},{"id":"/root/children/14/children/0","type":"text","loc":{"start":1668,"end":1681,"line":{"s":37,"e":37,"code":["### Data dynamics"]},"column":{"s":4,"e":17}},"dim":["","heading.14","text.0"],"code":"Data dynamics"},{"id":"/root/children/15","type":"list","loc":{"start":1683,"end":1831,"line":{"s":39,"e":41,"code":["- Webhooks: repo-sync, map-show tile generation","- Portlets concept (TODO)","- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)"]},"column":{"s":0,"e":74}},"dim":["","list.15"],"code":"- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)","symbName":"list","symbRange":[1833,1846],"symbRangeL":[39,44],"outerCode":"- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks","outerHtml":"<ul><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>"},{"id":"/root/children/15/children/0","type":"listItem","loc":{"start":1683,"end":1730,"line":{"s":39,"e":39,"code":["- Webhooks: repo-sync, map-show tile generation"]},"column":{"s":0,"e":47}},"dim":["","list.15","listItem.0"],"code":"- Webhooks: repo-sync, map-show tile generation"},{"id":"/root/children/15/children/0/children/0","type":"paragraph","loc":{"start":1685,"end":1730,"line":{"s":39,"e":39,"code":["- Webhooks: repo-sync, map-show tile generation"]},"column":{"s":2,"e":47}},"dim":["","list.15","listItem.0","paragraph.0"],"code":"Webhooks: repo-sync, map-show tile generation"},{"id":"/root/children/15/children/0/children/0/children/0","type":"text","loc":{"start":1685,"end":1730,"line":{"s":39,"e":39,"code":["- Webhooks: repo-sync, map-show tile generation"]},"column":{"s":2,"e":47}},"dim":["","list.15","listItem.0","paragraph.0","text.0"],"code":"Webhooks: repo-sync, map-show tile generation"},{"id":"/root/children/15/children/1","type":"listItem","loc":{"start":1731,"end":1756,"line":{"s":40,"e":40,"code":["- Portlets concept (TODO)"]},"column":{"s":0,"e":25}},"dim":["","list.15","listItem.1"],"code":"- Portlets concept (TODO)"},{"id":"/root/children/15/children/1/children/0","type":"paragraph","loc":{"start":1733,"end":1756,"line":{"s":40,"e":40,"code":["- Portlets concept (TODO)"]},"column":{"s":2,"e":25}},"dim":["","list.15","listItem.1","paragraph.0"],"code":"Portlets concept (TODO)"},{"id":"/root/children/15/children/1/children/0/children/0","type":"text","loc":{"start":1733,"end":1756,"line":{"s":40,"e":40,"code":["- Portlets concept (TODO)"]},"column":{"s":2,"e":25}},"dim":["","list.15","listItem.1","paragraph.0","text.0"],"code":"Portlets concept (TODO)"},{"id":"/root/children/15/children/2","type":"listItem","loc":{"start":1757,"end":1831,"line":{"s":41,"e":41,"code":["- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)"]},"column":{"s":0,"e":74}},"dim":["","list.15","listItem.2"],"code":"- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)"},{"id":"/root/children/15/children/2/children/0","type":"paragraph","loc":{"start":1759,"end":1831,"line":{"s":41,"e":41,"code":["- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)"]},"column":{"s":2,"e":74}},"dim":["","list.15","listItem.2","paragraph.0"],"code":"Singularity/plurarity: repolero subsystem of fragments and stages (TODO)"},{"id":"/root/children/15/children/2/children/0/children/0","type":"text","loc":{"start":1759,"end":1831,"line":{"s":41,"e":41,"code":["- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)"]},"column":{"s":2,"e":74}},"dim":["","list.15","listItem.2","paragraph.0","text.0"],"code":"Singularity/plurarity: repolero subsystem of fragments and stages (TODO)"},{"id":"/root/children/16","type":"heading","loc":{"start":1833,"end":1846,"line":{"s":43,"e":43,"code":["### UI tricks"]},"column":{"s":0,"e":13}},"dim":["","heading.16"],"code":"### UI tricks","symbName":"heading","symbRange":[1848,1895],"symbRangeL":[43,47],"outerCode":"\n- tippy.js for tooltips\n- icomoon icons as font","outerHtml":"\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>"},{"id":"/root/children/16/children/0","type":"text","loc":{"start":1837,"end":1846,"line":{"s":43,"e":43,"code":["### UI tricks"]},"column":{"s":4,"e":13}},"dim":["","heading.16","text.0"],"code":"UI tricks"},{"id":"/root/children/17","type":"list","loc":{"start":1848,"end":1895,"line":{"s":45,"e":46,"code":["- tippy.js for tooltips","- icomoon icons as font"]},"column":{"s":0,"e":23}},"dim":["","list.17"],"code":"- tippy.js for tooltips\n- icomoon icons as font","symbName":"list","symbRange":[1897,2033],"symbRangeL":[45,53],"outerCode":"- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles","outerHtml":"<ul><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>"},{"id":"/root/children/17/children/0","type":"listItem","loc":{"start":1848,"end":1871,"line":{"s":45,"e":45,"code":["- tippy.js for tooltips"]},"column":{"s":0,"e":23}},"dim":["","list.17","listItem.0"],"code":"- tippy.js for tooltips"},{"id":"/root/children/17/children/0/children/0","type":"paragraph","loc":{"start":1850,"end":1871,"line":{"s":45,"e":45,"code":["- tippy.js for tooltips"]},"column":{"s":2,"e":23}},"dim":["","list.17","listItem.0","paragraph.0"],"code":"tippy.js for tooltips"},{"id":"/root/children/17/children/0/children/0/children/0","type":"text","loc":{"start":1850,"end":1871,"line":{"s":45,"e":45,"code":["- tippy.js for tooltips"]},"column":{"s":2,"e":23}},"dim":["","list.17","listItem.0","paragraph.0","text.0"],"code":"tippy.js for tooltips"},{"id":"/root/children/17/children/1","type":"listItem","loc":{"start":1872,"end":1895,"line":{"s":46,"e":46,"code":["- icomoon icons as font"]},"column":{"s":0,"e":23}},"dim":["","list.17","listItem.1"],"code":"- icomoon icons as font"},{"id":"/root/children/17/children/1/children/0","type":"paragraph","loc":{"start":1874,"end":1895,"line":{"s":46,"e":46,"code":["- icomoon icons as font"]},"column":{"s":2,"e":23}},"dim":["","list.17","listItem.1","paragraph.0"],"code":"icomoon icons as font"},{"id":"/root/children/17/children/1/children/0/children/0","type":"text","loc":{"start":1874,"end":1895,"line":{"s":46,"e":46,"code":["- icomoon icons as font"]},"column":{"s":2,"e":23}},"dim":["","list.17","listItem.1","paragraph.0","text.0"],"code":"icomoon icons as font"},{"id":"/root/children/18","type":"heading","loc":{"start":1897,"end":1941,"line":{"s":48,"e":48,"code":["## ssss — super simple static site generator"]},"column":{"s":0,"e":44}},"dim":["","heading.18"],"code":"## ssss — super simple static site generator","symbName":"heading","symbRange":[1943,2017],"symbRangeL":[48,51],"outerCode":"\nrepolero-prototype is built with **ssss**, a custom static site generator.","outerHtml":"\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>"},{"id":"/root/children/18/children/0","type":"text","loc":{"start":1900,"end":1941,"line":{"s":48,"e":48,"code":["## ssss — super simple static site generator"]},"column":{"s":3,"e":44}},"dim":["","heading.18","text.0"],"code":"ssss — super simple static site generator"},{"id":"/root/children/19","type":"paragraph","loc":{"start":1943,"end":2017,"line":{"s":50,"e":50,"code":["repolero-prototype is built with **ssss**, a custom static site generator."]},"column":{"s":0,"e":74}},"dim":["","paragraph.19"],"code":"repolero-prototype is built with **ssss**, a custom static site generator."},{"id":"/root/children/19/children/0","type":"text","loc":{"start":1943,"end":1976,"line":{"s":50,"e":50,"code":["repolero-prototype is built with **ssss**, a custom static site generator."]},"column":{"s":0,"e":33}},"dim":["","paragraph.19","text.0"],"code":"repolero-prototype is built with "},{"id":"/root/children/19/children/1","type":"strong","loc":{"start":1976,"end":1984,"line":{"s":50,"e":50,"code":["repolero-prototype is built with **ssss**, a custom static site generator."]},"column":{"s":33,"e":41}},"dim":["","paragraph.19","strong.1"],"code":"**ssss**"},{"id":"/root/children/19/children/1/children/0","type":"text","loc":{"start":1978,"end":1982,"line":{"s":50,"e":50,"code":["repolero-prototype is built with **ssss**, a custom static site generator."]},"column":{"s":35,"e":39}},"dim":["","paragraph.19","strong.1","text.0"],"code":"ssss"},{"id":"/root/children/19/children/2","type":"text","loc":{"start":1984,"end":2017,"line":{"s":50,"e":50,"code":["repolero-prototype is built with **ssss**, a custom static site generator."]},"column":{"s":41,"e":74}},"dim":["","paragraph.19","text.2"],"code":", a custom static site generator."},{"id":"/root/children/20","type":"heading","loc":{"start":2019,"end":2033,"line":{"s":52,"e":52,"code":["### Principles"]},"column":{"s":0,"e":14}},"dim":["","heading.20"],"code":"### Principles","symbName":"heading","symbRange":[2035,2261],"symbRangeL":[52,56],"outerCode":"\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`","outerHtml":"\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>"},{"id":"/root/children/20/children/0","type":"text","loc":{"start":2023,"end":2033,"line":{"s":52,"e":52,"code":["### Principles"]},"column":{"s":4,"e":14}},"dim":["","heading.20","text.0"],"code":"Principles"},{"id":"/root/children/21","type":"list","loc":{"start":2035,"end":2261,"line":{"s":54,"e":55,"code":["- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment","- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":0,"e":131}},"dim":["","list.21"],"code":"- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`","symbName":"list","symbRange":[2263,7475],"symbRangeL":[54,155],"outerCode":"- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**","outerHtml":"<ul><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>"},{"id":"/root/children/21/children/0","type":"listItem","loc":{"start":2035,"end":2129,"line":{"s":54,"e":54,"code":["- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment"]},"column":{"s":0,"e":94}},"dim":["","list.21","listItem.0"],"code":"- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment"},{"id":"/root/children/21/children/0/children/0","type":"paragraph","loc":{"start":2037,"end":2129,"line":{"s":54,"e":54,"code":["- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment"]},"column":{"s":2,"e":94}},"dim":["","list.21","listItem.0","paragraph.0"],"code":"**Petri-net flow**: many sources flow into one output, many outputs flow into one deployment"},{"id":"/root/children/21/children/0/children/0/children/0","type":"strong","loc":{"start":2037,"end":2055,"line":{"s":54,"e":54,"code":["- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment"]},"column":{"s":2,"e":20}},"dim":["","list.21","listItem.0","paragraph.0","strong.0"],"code":"**Petri-net flow**"},{"id":"/root/children/21/children/0/children/0/children/0/children/0","type":"text","loc":{"start":2039,"end":2053,"line":{"s":54,"e":54,"code":["- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment"]},"column":{"s":4,"e":18}},"dim":["","list.21","listItem.0","paragraph.0","strong.0","text.0"],"code":"Petri-net flow"},{"id":"/root/children/21/children/0/children/0/children/1","type":"text","loc":{"start":2055,"end":2129,"line":{"s":54,"e":54,"code":["- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment"]},"column":{"s":20,"e":94}},"dim":["","list.21","listItem.0","paragraph.0","text.1"],"code":": many sources flow into one output, many outputs flow into one deployment"},{"id":"/root/children/21/children/1","type":"listItem","loc":{"start":2130,"end":2261,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":0,"e":131}},"dim":["","list.21","listItem.1"],"code":"- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"},{"id":"/root/children/21/children/1/children/0","type":"paragraph","loc":{"start":2132,"end":2261,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":2,"e":131}},"dim":["","list.21","listItem.1","paragraph.0"],"code":"**JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"},{"id":"/root/children/21/children/1/children/0/children/0","type":"strong","loc":{"start":2132,"end":2159,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":2,"e":29}},"dim":["","list.21","listItem.1","paragraph.0","strong.0"],"code":"**JSON inheritance (meld)**"},{"id":"/root/children/21/children/1/children/0/children/0/children/0","type":"text","loc":{"start":2134,"end":2157,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":4,"e":27}},"dim":["","list.21","listItem.1","paragraph.0","strong.0","text.0"],"code":"JSON inheritance (meld)"},{"id":"/root/children/21/children/1/children/0/children/1","type":"text","loc":{"start":2159,"end":2182,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":29,"e":52}},"dim":["","list.21","listItem.1","paragraph.0","text.1"],"code":": configs merge via jq "},{"id":"/root/children/21/children/1/children/0/children/2","type":"inlineCode","loc":{"start":2182,"end":2190,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":52,"e":60}},"dim":["","list.21","listItem.1","paragraph.0","inlineCode.2"],"code":"`meld()`"},{"id":"/root/children/21/children/1/children/0/children/3","type":"text","loc":{"start":2190,"end":2198,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":60,"e":68}},"dim":["","list.21","listItem.1","paragraph.0","text.3"],"code":" — e.g. "},{"id":"/root/children/21/children/1/children/0/children/4","type":"inlineCode","loc":{"start":2198,"end":2261,"line":{"s":55,"e":55,"code":["- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"]},"column":{"s":68,"e":131}},"dim":["","list.21","listItem.1","paragraph.0","inlineCode.4"],"code":"`cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`"},{"id":"/root/children/22","type":"heading","loc":{"start":2263,"end":2278,"line":{"s":57,"e":57,"code":["### MPA concept"]},"column":{"s":0,"e":15}},"dim":["","heading.22"],"code":"### MPA concept","symbName":"heading","symbRange":[2280,2398],"symbRangeL":[57,60],"outerCode":"\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.","outerHtml":"\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>"},{"id":"/root/children/22/children/0","type":"text","loc":{"start":2267,"end":2278,"line":{"s":57,"e":57,"code":["### MPA concept"]},"column":{"s":4,"e":15}},"dim":["","heading.22","text.0"],"code":"MPA concept"},{"id":"/root/children/23","type":"paragraph","loc":{"start":2280,"end":2398,"line":{"s":59,"e":59,"code":["Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers."]},"column":{"s":0,"e":118}},"dim":["","paragraph.23"],"code":"Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers."},{"id":"/root/children/23/children/0","type":"text","loc":{"start":2280,"end":2398,"line":{"s":59,"e":59,"code":["Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers."]},"column":{"s":0,"e":118}},"dim":["","paragraph.23","text.0"],"code":"Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers."},{"id":"/root/children/24","type":"heading","loc":{"start":2400,"end":2420,"line":{"s":61,"e":61,"code":["### Folder structure"]},"column":{"s":0,"e":20}},"dim":["","heading.24"],"code":"### Folder structure","symbName":"heading","symbRange":[2422,3135],"symbRangeL":[61,83],"outerCode":"\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```","outerHtml":"\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>"},{"id":"/root/children/24/children/0","type":"text","loc":{"start":2404,"end":2420,"line":{"s":61,"e":61,"code":["### Folder structure"]},"column":{"s":4,"e":20}},"dim":["","heading.24","text.0"],"code":"Folder structure"},{"id":"/root/children/25","type":"code","loc":{"start":2422,"end":3135,"line":{"s":63,"e":82,"code":["```","ssss/","  assets/","    images/*.png","    styles/*.less","    scripts/lib.js          — ssss core client-side JS (ssss.js)","    scripts/*.js","  data/*                    — primary data (e.g. nljson)","source/","  s4.config.json            — run ssss-run from here","  *.jq.json                 — declares a page to render","  *.jq.html                 — template (or linked from theme via jq.json)","  *                         — adjacent to ssss/*","output/ (aka dist/)","  assets/                   ← ssss/assets + source/assets merged","  *.html                    ← source/*.jq.json + *.jq.html rendered","  data/**                   ← sfsDir + tot","deploy/","  rsync mirror of output/   — final destination (many sources converge)","```"]},"column":{"s":0,"e":3}},"dim":["","code.25"],"code":"```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```","symbName":"code","symbRange":[3137,5154],"symbRangeL":[null,110],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>"},{"id":"/root/children/26","type":"heading","loc":{"start":3137,"end":3169,"line":{"s":84,"e":84,"code":["### Tech choices (from ssss.mdd)"]},"column":{"s":0,"e":32}},"dim":["","heading.26"],"code":"### Tech choices (from ssss.mdd)","symbName":"heading","symbRange":[3171,4834],"symbRangeL":[84,102],"outerCode":"\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |","outerHtml":"\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>"},{"id":"/root/children/26/children/0","type":"text","loc":{"start":3141,"end":3169,"line":{"s":84,"e":84,"code":["### Tech choices (from ssss.mdd)"]},"column":{"s":4,"e":32}},"dim":["","heading.26","text.0"],"code":"Tech choices (from ssss.mdd)"},{"id":"/root/children/27","type":"paragraph","loc":{"start":3171,"end":4834,"line":{"s":86,"e":101,"code":["| Category           | Winner / in use             | Alternatives considered                          |","| ------------------ | --------------------------- | ------------------------------------------------ |","| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |","| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |","| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |","| State management   | valtio                      | zustand, jotai, xstate                           |","| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |","| Charts             | Vega-Lite                   | —                                                |","| Drag & drop        | dragula                     | —                                                |","| Notifications      | humane-js (Notyf in player) | —                                                |","| Search             | typesense, algolia          | —                                                |","| Modal              | tingle, micromodal          | —                                                |","| Fetch              | TwinSpark, aja.js           | —                                                |","| JSON rendering     | jsonToHtml-render           | —                                                |","| Store              | nbubna/store                | websheet, firebase                               |","| GUI layout         | dockview                    | —                                                |"]},"column":{"s":0,"e":103}},"dim":["","paragraph.27"],"code":"| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |"},{"id":"/root/children/27/children/0","type":"text","loc":{"start":3171,"end":4834,"line":{"s":86,"e":101,"code":["| Category           | Winner / in use             | Alternatives considered                          |","| ------------------ | --------------------------- | ------------------------------------------------ |","| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |","| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |","| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |","| State management   | valtio                      | zustand, jotai, xstate                           |","| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |","| Charts             | Vega-Lite                   | —                                                |","| Drag & drop        | dragula                     | —                                                |","| Notifications      | humane-js (Notyf in player) | —                                                |","| Search             | typesense, algolia          | —                                                |","| Modal              | tingle, micromodal          | —                                                |","| Fetch              | TwinSpark, aja.js           | —                                                |","| JSON rendering     | jsonToHtml-render           | —                                                |","| Store              | nbubna/store                | websheet, firebase                               |","| GUI layout         | dockview                    | —                                                |"]},"column":{"s":0,"e":103}},"dim":["","paragraph.27","text.0"],"code":"| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |"},{"id":"/root/children/28","type":"heading","loc":{"start":4836,"end":4845,"line":{"s":103,"e":103,"code":["### Usage"]},"column":{"s":0,"e":9}},"dim":["","heading.28"],"code":"### Usage","symbName":"heading","symbRange":[4847,4883],"symbRangeL":[103,106],"outerCode":"\n`yarn serve`, `yarn watch`, `yoffee`","outerHtml":"\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>"},{"id":"/root/children/28/children/0","type":"text","loc":{"start":4840,"end":4845,"line":{"s":103,"e":103,"code":["### Usage"]},"column":{"s":4,"e":9}},"dim":["","heading.28","text.0"],"code":"Usage"},{"id":"/root/children/29","type":"paragraph","loc":{"start":4847,"end":4883,"line":{"s":105,"e":105,"code":["`yarn serve`, `yarn watch`, `yoffee`"]},"column":{"s":0,"e":36}},"dim":["","paragraph.29"],"code":"`yarn serve`, `yarn watch`, `yoffee`"},{"id":"/root/children/29/children/0","type":"inlineCode","loc":{"start":4847,"end":4859,"line":{"s":105,"e":105,"code":["`yarn serve`, `yarn watch`, `yoffee`"]},"column":{"s":0,"e":12}},"dim":["","paragraph.29","inlineCode.0"],"code":"`yarn serve`"},{"id":"/root/children/29/children/1","type":"text","loc":{"start":4859,"end":4861,"line":{"s":105,"e":105,"code":["`yarn serve`, `yarn watch`, `yoffee`"]},"column":{"s":12,"e":14}},"dim":["","paragraph.29","text.1"],"code":", "},{"id":"/root/children/29/children/2","type":"inlineCode","loc":{"start":4861,"end":4873,"line":{"s":105,"e":105,"code":["`yarn serve`, `yarn watch`, `yoffee`"]},"column":{"s":14,"e":26}},"dim":["","paragraph.29","inlineCode.2"],"code":"`yarn watch`"},{"id":"/root/children/29/children/3","type":"text","loc":{"start":4873,"end":4875,"line":{"s":105,"e":105,"code":["`yarn serve`, `yarn watch`, `yoffee`"]},"column":{"s":26,"e":28}},"dim":["","paragraph.29","text.3"],"code":", "},{"id":"/root/children/29/children/4","type":"inlineCode","loc":{"start":4875,"end":4883,"line":{"s":105,"e":105,"code":["`yarn serve`, `yarn watch`, `yoffee`"]},"column":{"s":28,"e":36}},"dim":["","paragraph.29","inlineCode.4"],"code":"`yoffee`"},{"id":"/root/children/30","type":"heading","loc":{"start":4885,"end":4902,"line":{"s":107,"e":107,"code":["## Build Pipeline"]},"column":{"s":0,"e":17}},"dim":["","heading.30"],"code":"## Build Pipeline","symbName":"heading","symbRange":[4904,8063],"symbRangeL":[107,166],"outerCode":"\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`","outerHtml":"\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>"},{"id":"/root/children/30/children/0","type":"text","loc":{"start":4888,"end":4902,"line":{"s":107,"e":107,"code":["## Build Pipeline"]},"column":{"s":3,"e":17}},"dim":["","heading.30","text.0"],"code":"Build Pipeline"},{"id":"/root/children/31","type":"paragraph","loc":{"start":4904,"end":5154,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":0,"e":250}},"dim":["","paragraph.31"],"code":"Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"},{"id":"/root/children/31/children/0","type":"text","loc":{"start":4904,"end":4909,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":0,"e":5}},"dim":["","paragraph.31","text.0"],"code":"Each "},{"id":"/root/children/31/children/1","type":"inlineCode","loc":{"start":4909,"end":4919,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":5,"e":15}},"dim":["","paragraph.31","inlineCode.1"],"code":"`.jq.html`"},{"id":"/root/children/31/children/2","type":"text","loc":{"start":4919,"end":4932,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":15,"e":28}},"dim":["","paragraph.31","text.2"],"code":" template in "},{"id":"/root/children/31/children/3","type":"inlineCode","loc":{"start":4932,"end":4941,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":28,"e":37}},"dim":["","paragraph.31","inlineCode.3"],"code":"`source/`"},{"id":"/root/children/31/children/4","type":"text","loc":{"start":4941,"end":4971,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":37,"e":67}},"dim":["","paragraph.31","text.4"],"code":" has a corresponding rendered "},{"id":"/root/children/31/children/5","type":"inlineCode","loc":{"start":4971,"end":4978,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":67,"e":74}},"dim":["","paragraph.31","inlineCode.5"],"code":"`.html`"},{"id":"/root/children/31/children/6","type":"text","loc":{"start":4978,"end":4982,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":74,"e":78}},"dim":["","paragraph.31","text.6"],"code":" in "},{"id":"/root/children/31/children/7","type":"inlineCode","loc":{"start":4982,"end":4989,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":78,"e":85}},"dim":["","paragraph.31","inlineCode.7"],"code":"`dist/`"},{"id":"/root/children/31/children/8","type":"text","loc":{"start":4989,"end":5023,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":85,"e":119}},"dim":["","paragraph.31","text.8"],"code":". Each template also has a paired "},{"id":"/root/children/31/children/9","type":"inlineCode","loc":{"start":5023,"end":5033,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":119,"e":129}},"dim":["","paragraph.31","inlineCode.9"],"code":"`.jq.json`"},{"id":"/root/children/31/children/10","type":"text","loc":{"start":5033,"end":5154,"line":{"s":109,"e":109,"code":["Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"]},"column":{"s":129,"e":250}},"dim":["","paragraph.31","text.10"],"code":" config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):"},{"id":"/root/children/32","type":"code","loc":{"start":5156,"end":5587,"line":{"s":111,"e":120,"code":["```","source/repolero-prototype/view.jq.json  →  data for view.jq.html","source/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html","","source/repolero-prototype/rc.jq.json    →  data for rc.jq.html","source/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html","","source/repolero-prototype/home.jq.json  →  data for home.jq.html","source/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html","```"]},"column":{"s":0,"e":3}},"dim":["","code.32"],"code":"```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```","symbName":"code","symbRange":[5589,6473],"symbRangeL":[null,128],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>"},{"id":"/root/children/33","type":"paragraph","loc":{"start":5589,"end":5691,"line":{"s":122,"e":123,"code":["**All jq.html pages:**","view, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)"]},"column":{"s":0,"e":79}},"dim":["","paragraph.33"],"code":"**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)"},{"id":"/root/children/33/children/0","type":"strong","loc":{"start":5589,"end":5611,"line":{"s":122,"e":122,"code":["**All jq.html pages:**"]},"column":{"s":0,"e":22}},"dim":["","paragraph.33","strong.0"],"code":"**All jq.html pages:**"},{"id":"/root/children/33/children/0/children/0","type":"text","loc":{"start":5591,"end":5609,"line":{"s":122,"e":122,"code":["**All jq.html pages:**"]},"column":{"s":2,"e":20}},"dim":["","paragraph.33","strong.0","text.0"],"code":"All jq.html pages:"},{"id":"/root/children/33/children/1","type":"text","loc":{"start":5611,"end":5691,"line":{"s":122,"e":123,"code":["**All jq.html pages:**","view, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)"]},"column":{"s":22,"e":79}},"dim":["","paragraph.33","text.1"],"code":"\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)"},{"id":"/root/children/34","type":"paragraph","loc":{"start":5693,"end":6422,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":0,"e":729}},"dim":["","paragraph.34"],"code":"`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."},{"id":"/root/children/34/children/0","type":"inlineCode","loc":{"start":5693,"end":5710,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":0,"e":17}},"dim":["","paragraph.34","inlineCode.0"],"code":"`reflect.jq.html`"},{"id":"/root/children/34/children/1","type":"text","loc":{"start":5710,"end":5735,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":17,"e":42}},"dim":["","paragraph.34","text.1"],"code":" (545 lines, paired with "},{"id":"/root/children/34/children/2","type":"inlineCode","loc":{"start":5735,"end":5752,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":42,"e":59}},"dim":["","paragraph.34","inlineCode.2"],"code":"`reflect.jq.json`"},{"id":"/root/children/34/children/3","type":"text","loc":{"start":5752,"end":5760,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":59,"e":67}},"dim":["","paragraph.34","text.3"],"code":", title "},{"id":"/root/children/34/children/4","type":"inlineCode","loc":{"start":5760,"end":5778,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":67,"e":85}},"dim":["","paragraph.34","inlineCode.4"],"code":"`repolero:reflect`"},{"id":"/root/children/34/children/5","type":"text","loc":{"start":5778,"end":5787,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":85,"e":94}},"dim":["","paragraph.34","text.5"],"code":") is the "},{"id":"/root/children/34/children/6","type":"strong","loc":{"start":5787,"end":5815,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":94,"e":122}},"dim":["","paragraph.34","strong.6"],"code":"**alternative player shell**"},{"id":"/root/children/34/children/6/children/0","type":"text","loc":{"start":5789,"end":5813,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":96,"e":120}},"dim":["","paragraph.34","strong.6","text.0"],"code":"alternative player shell"},{"id":"/root/children/34/children/7","type":"text","loc":{"start":5815,"end":5880,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":122,"e":187}},"dim":["","paragraph.34","text.7"],"code":" introduced for tenant subdomains. Mode is the full feature set ("},{"id":"/root/children/34/children/8","type":"inlineCode","loc":{"start":5880,"end":5965,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":187,"e":272}},"dim":["","paragraph.34","inlineCode.8"],"code":"`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`"},{"id":"/root/children/34/children/9","type":"text","loc":{"start":5965,"end":5989,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":272,"e":296}},"dim":["","paragraph.34","text.9"],"code":"), so it boots the same "},{"id":"/root/children/34/children/10","type":"inlineCode","loc":{"start":5989,"end":5999,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":296,"e":306}},"dim":["","paragraph.34","inlineCode.10"],"code":"`player()`"},{"id":"/root/children/34/children/11","type":"text","loc":{"start":5999,"end":6003,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":306,"e":310}},"dim":["","paragraph.34","text.11"],"code":" as "},{"id":"/root/children/34/children/12","type":"inlineCode","loc":{"start":6003,"end":6017,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":310,"e":324}},"dim":["","paragraph.34","inlineCode.12"],"code":"`view.jq.html`"},{"id":"/root/children/34/children/13","type":"text","loc":{"start":6017,"end":6091,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":324,"e":398}},"dim":["","paragraph.34","text.13"],"code":" but with a different layout/skin tailored for tenant entry. Caddy serves "},{"id":"/root/children/34/children/14","type":"inlineCode","loc":{"start":6091,"end":6106,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":398,"e":413}},"dim":["","paragraph.34","inlineCode.14"],"code":"`/reflect.html`"},{"id":"/root/children/34/children/15","type":"text","loc":{"start":6106,"end":6112,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":413,"e":419}},"dim":["","paragraph.34","text.15"],"code":" (not "},{"id":"/root/children/34/children/16","type":"inlineCode","loc":{"start":6112,"end":6124,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":419,"e":431}},"dim":["","paragraph.34","inlineCode.16"],"code":"`/view.html`"},{"id":"/root/children/34/children/17","type":"text","loc":{"start":6124,"end":6198,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":431,"e":505}},"dim":["","paragraph.34","text.17"],"code":") for the subset of tenant subdomains opted into the reflect layout — see "},{"id":"/root/children/34/children/18","type":"inlineCode","loc":{"start":6198,"end":6251,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":505,"e":558}},"dim":["","paragraph.34","inlineCode.18"],"code":"`linode-server/caddy/local/Caddyfile.local.caddyfile`"},{"id":"/root/children/34/children/19","type":"text","loc":{"start":6251,"end":6256,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":558,"e":563}},"dim":["","paragraph.34","text.19"],"code":" and "},{"id":"/root/children/34/children/20","type":"inlineCode","loc":{"start":6256,"end":6294,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":563,"e":601}},"dim":["","paragraph.34","inlineCode.20"],"code":"`…/linode/Caddyfile.contabo.caddyfile`"},{"id":"/root/children/34/children/21","type":"text","loc":{"start":6294,"end":6310,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":601,"e":617}},"dim":["","paragraph.34","text.21"],"code":". Currently the "},{"id":"/root/children/34/children/22","type":"inlineCode","loc":{"start":6310,"end":6318,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":617,"e":625}},"dim":["","paragraph.34","inlineCode.22"],"code":"`makata`"},{"id":"/root/children/34/children/23","type":"text","loc":{"start":6318,"end":6331,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":625,"e":638}},"dim":["","paragraph.34","text.23"],"code":" tenant uses "},{"id":"/root/children/34/children/24","type":"inlineCode","loc":{"start":6331,"end":6346,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":638,"e":653}},"dim":["","paragraph.34","inlineCode.24"],"code":"`/reflect.html`"},{"id":"/root/children/34/children/25","type":"text","loc":{"start":6346,"end":6363,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":653,"e":670}},"dim":["","paragraph.34","text.25"],"code":"; other tenants ("},{"id":"/root/children/34/children/26","type":"inlineCode","loc":{"start":6363,"end":6372,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":670,"e":679}},"dim":["","paragraph.34","inlineCode.26"],"code":"`massage`"},{"id":"/root/children/34/children/27","type":"text","loc":{"start":6372,"end":6374,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":679,"e":681}},"dim":["","paragraph.34","text.27"],"code":", "},{"id":"/root/children/34/children/28","type":"inlineCode","loc":{"start":6374,"end":6382,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":681,"e":689}},"dim":["","paragraph.34","inlineCode.28"],"code":"`maxina`"},{"id":"/root/children/34/children/29","type":"text","loc":{"start":6382,"end":6409,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":689,"e":716}},"dim":["","paragraph.34","text.29"],"code":", …) still fall through to "},{"id":"/root/children/34/children/30","type":"inlineCode","loc":{"start":6409,"end":6421,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":716,"e":728}},"dim":["","paragraph.34","inlineCode.30"],"code":"`/view.html`"},{"id":"/root/children/34/children/31","type":"text","loc":{"start":6421,"end":6422,"line":{"s":125,"e":125,"code":["`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`."]},"column":{"s":728,"e":729}},"dim":["","paragraph.34","text.31"],"code":"."},{"id":"/root/children/35","type":"paragraph","loc":{"start":6424,"end":6473,"line":{"s":127,"e":127,"code":["**The .jq.json structure** (shared across pages):"]},"column":{"s":0,"e":49}},"dim":["","paragraph.35"],"code":"**The .jq.json structure** (shared across pages):"},{"id":"/root/children/35/children/0","type":"strong","loc":{"start":6424,"end":6450,"line":{"s":127,"e":127,"code":["**The .jq.json structure** (shared across pages):"]},"column":{"s":0,"e":26}},"dim":["","paragraph.35","strong.0"],"code":"**The .jq.json structure**"},{"id":"/root/children/35/children/0/children/0","type":"text","loc":{"start":6426,"end":6448,"line":{"s":127,"e":127,"code":["**The .jq.json structure** (shared across pages):"]},"column":{"s":2,"e":24}},"dim":["","paragraph.35","strong.0","text.0"],"code":"The .jq.json structure"},{"id":"/root/children/35/children/1","type":"text","loc":{"start":6450,"end":6473,"line":{"s":127,"e":127,"code":["**The .jq.json structure** (shared across pages):"]},"column":{"s":26,"e":49}},"dim":["","paragraph.35","text.1"],"code":" (shared across pages):"},{"id":"/root/children/36","type":"code","loc":{"start":6475,"end":7089,"line":{"s":129,"e":148,"code":["```","{","  \"name\": \"open-city\",","  \"repoName\": \"repolero\",","  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build","  \"site\": {","    \"ver\", \"env\", \"lang\", \"theme\",","    \"mode\": \"admin,self,water,...\",          // feature flags","    \"title\": \"repolero:view\",","    \"logoMini\", \"fragments\", \"menu.pages\",","    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config","    \"touchbar\": { paper: [...svgs] }","  },","  \"head\": {","    \"link\": [ { href, rel, type, tag } ],   // CSS deps","    \"script\": [ { src, type, tag } ]         // JS deps","  },","  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.36"],"code":"```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```","symbName":"code","symbRange":[7091,9640],"symbRangeL":[null,190],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>"},{"id":"/root/children/37","type":"paragraph","loc":{"start":7091,"end":7271,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":0,"e":180}},"dim":["","paragraph.37"],"code":"The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."},{"id":"/root/children/37/children/0","type":"text","loc":{"start":7091,"end":7095,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":0,"e":4}},"dim":["","paragraph.37","text.0"],"code":"The "},{"id":"/root/children/37/children/1","type":"inlineCode","loc":{"start":7095,"end":7101,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":4,"e":10}},"dim":["","paragraph.37","inlineCode.1"],"code":"`mode`"},{"id":"/root/children/37/children/2","type":"text","loc":{"start":7101,"end":7152,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":10,"e":61}},"dim":["","paragraph.37","text.2"],"code":" field controls which CSS/JS libs are included via "},{"id":"/root/children/37/children/3","type":"inlineCode","loc":{"start":7152,"end":7178,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":61,"e":87}},"dim":["","paragraph.37","inlineCode.3"],"code":"`s4FilterIncludes($input)`"},{"id":"/root/children/37/children/4","type":"text","loc":{"start":7178,"end":7203,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":87,"e":112}},"dim":["","paragraph.37","text.4"],"code":". E.g. rc.jq.json has no "},{"id":"/root/children/37/children/5","type":"inlineCode","loc":{"start":7203,"end":7209,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":112,"e":118}},"dim":["","paragraph.37","inlineCode.5"],"code":"`vega`"},{"id":"/root/children/37/children/6","type":"text","loc":{"start":7209,"end":7262,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":118,"e":171}},"dim":["","paragraph.37","text.6"],"code":" in mode, so Vega-Lite scripts are excluded from its "},{"id":"/root/children/37/children/7","type":"inlineCode","loc":{"start":7262,"end":7270,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":171,"e":179}},"dim":["","paragraph.37","inlineCode.7"],"code":"`<head>`"},{"id":"/root/children/37/children/8","type":"text","loc":{"start":7270,"end":7271,"line":{"s":150,"e":150,"code":["The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`."]},"column":{"s":179,"e":180}},"dim":["","paragraph.37","text.8"],"code":"."},{"id":"/root/children/38","type":"paragraph","loc":{"start":7273,"end":7447,"line":{"s":152,"e":152,"code":["The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax."]},"column":{"s":0,"e":174}},"dim":["","paragraph.38"],"code":"The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax."},{"id":"/root/children/38/children/0","type":"text","loc":{"start":7273,"end":7326,"line":{"s":152,"e":152,"code":["The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax."]},"column":{"s":0,"e":53}},"dim":["","paragraph.38","text.0"],"code":"The jq-html engine runs server-side and resolves all "},{"id":"/root/children/38/children/1","type":"inlineCode","loc":{"start":7326,"end":7334,"line":{"s":152,"e":152,"code":["The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax."]},"column":{"s":53,"e":61}},"dim":["","paragraph.38","inlineCode.1"],"code":"`¦expr¦`"},{"id":"/root/children/38/children/2","type":"text","loc":{"start":7334,"end":7401,"line":{"s":152,"e":152,"code":["The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax."]},"column":{"s":61,"e":128}},"dim":["","paragraph.38","text.2"],"code":" expressions into concrete values. The browser only ever loads the "},{"id":"/root/children/38/children/3","type":"inlineCode","loc":{"start":7401,"end":7414,"line":{"s":152,"e":152,"code":["The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax."]},"column":{"s":128,"e":141}},"dim":["","paragraph.38","inlineCode.3"],"code":"`dist/*.html`"},{"id":"/root/children/38/children/4","type":"text","loc":{"start":7414,"end":7447,"line":{"s":152,"e":152,"code":["The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax."]},"column":{"s":141,"e":174}},"dim":["","paragraph.38","text.4"],"code":" files — it never sees jq syntax."},{"id":"/root/children/39","type":"paragraph","loc":{"start":7449,"end":7475,"line":{"s":154,"e":154,"code":["**What gets transformed:**"]},"column":{"s":0,"e":26}},"dim":["","paragraph.39"],"code":"**What gets transformed:**"},{"id":"/root/children/39/children/0","type":"strong","loc":{"start":7449,"end":7475,"line":{"s":154,"e":154,"code":["**What gets transformed:**"]},"column":{"s":0,"e":26}},"dim":["","paragraph.39","strong.0"],"code":"**What gets transformed:**"},{"id":"/root/children/39/children/0/children/0","type":"text","loc":{"start":7451,"end":7473,"line":{"s":154,"e":154,"code":["**What gets transformed:**"]},"column":{"s":2,"e":24}},"dim":["","paragraph.39","strong.0","text.0"],"code":"What gets transformed:"},{"id":"/root/children/40","type":"list","loc":{"start":7477,"end":7788,"line":{"s":156,"e":159,"code":["- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`","- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`","- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements","- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"]},"column":{"s":0,"e":73}},"dim":["","list.40"],"code":"- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`","symbName":"list","symbRange":[7790,7824],"symbRangeL":[156,162],"outerCode":"- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**","outerHtml":"<ul><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>"},{"id":"/root/children/40/children/0","type":"listItem","loc":{"start":7477,"end":7565,"line":{"s":156,"e":156,"code":["- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"]},"column":{"s":0,"e":88}},"dim":["","list.40","listItem.0"],"code":"- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"},{"id":"/root/children/40/children/0/children/0","type":"paragraph","loc":{"start":7479,"end":7565,"line":{"s":156,"e":156,"code":["- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"]},"column":{"s":2,"e":88}},"dim":["","list.40","listItem.0","paragraph.0"],"code":"HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"},{"id":"/root/children/40/children/0/children/0/children/0","type":"text","loc":{"start":7479,"end":7496,"line":{"s":156,"e":156,"code":["- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"]},"column":{"s":2,"e":19}},"dim":["","list.40","listItem.0","paragraph.0","text.0"],"code":"HTML attributes: "},{"id":"/root/children/40/children/0/children/0/children/1","type":"inlineCode","loc":{"start":7496,"end":7525,"line":{"s":156,"e":156,"code":["- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"]},"column":{"s":19,"e":48}},"dim":["","list.40","listItem.0","paragraph.0","inlineCode.1"],"code":"`href='¦\"assets://x\"|s4url¦'`"},{"id":"/root/children/40/children/0/children/0/children/2","type":"text","loc":{"start":7525,"end":7528,"line":{"s":156,"e":156,"code":["- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"]},"column":{"s":48,"e":51}},"dim":["","list.40","listItem.0","paragraph.0","text.2"],"code":" → "},{"id":"/root/children/40/children/0/children/0/children/3","type":"inlineCode","loc":{"start":7528,"end":7565,"line":{"s":156,"e":156,"code":["- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`"]},"column":{"s":51,"e":88}},"dim":["","list.40","listItem.0","paragraph.0","inlineCode.3"],"code":"`href='http://assets.untiloop.loc/x'`"},{"id":"/root/children/40/children/1","type":"listItem","loc":{"start":7566,"end":7643,"line":{"s":157,"e":157,"code":["- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"]},"column":{"s":0,"e":77}},"dim":["","list.40","listItem.1"],"code":"- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"},{"id":"/root/children/40/children/1/children/0","type":"paragraph","loc":{"start":7568,"end":7643,"line":{"s":157,"e":157,"code":["- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"]},"column":{"s":2,"e":77}},"dim":["","list.40","listItem.1","paragraph.0"],"code":"Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"},{"id":"/root/children/40/children/1/children/0/children/0","type":"text","loc":{"start":7568,"end":7582,"line":{"s":157,"e":157,"code":["- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"]},"column":{"s":2,"e":16}},"dim":["","list.40","listItem.1","paragraph.0","text.0"],"code":"Text content: "},{"id":"/root/children/40/children/1/children/0/children/1","type":"inlineCode","loc":{"start":7582,"end":7612,"line":{"s":157,"e":157,"code":["- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"]},"column":{"s":16,"e":46}},"dim":["","list.40","listItem.1","paragraph.0","inlineCode.1"],"code":"`<title>¦.site.title¦</title>`"},{"id":"/root/children/40/children/1/children/0/children/2","type":"text","loc":{"start":7612,"end":7615,"line":{"s":157,"e":157,"code":["- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"]},"column":{"s":46,"e":49}},"dim":["","list.40","listItem.1","paragraph.0","text.2"],"code":" → "},{"id":"/root/children/40/children/1/children/0/children/3","type":"inlineCode","loc":{"start":7615,"end":7643,"line":{"s":157,"e":157,"code":["- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`"]},"column":{"s":49,"e":77}},"dim":["","list.40","listItem.1","paragraph.0","inlineCode.3"],"code":"`<title>repolero:rc</title>`"},{"id":"/root/children/40/children/2","type":"listItem","loc":{"start":7644,"end":7714,"line":{"s":158,"e":158,"code":["- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"]},"column":{"s":0,"e":70}},"dim":["","list.40","listItem.2"],"code":"- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"},{"id":"/root/children/40/children/2/children/0","type":"paragraph","loc":{"start":7646,"end":7714,"line":{"s":158,"e":158,"code":["- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"]},"column":{"s":2,"e":70}},"dim":["","list.40","listItem.2","paragraph.0"],"code":"Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"},{"id":"/root/children/40/children/2/children/0/children/0","type":"text","loc":{"start":7646,"end":7665,"line":{"s":158,"e":158,"code":["- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"]},"column":{"s":2,"e":21}},"dim":["","list.40","listItem.2","paragraph.0","text.0"],"code":"Element iteration: "},{"id":"/root/children/40/children/2/children/0/children/1","type":"inlineCode","loc":{"start":7665,"end":7690,"line":{"s":158,"e":158,"code":["- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"]},"column":{"s":21,"e":46}},"dim":["","list.40","listItem.2","paragraph.0","inlineCode.1"],"code":"`<option ¦=\".items|.[]\">`"},{"id":"/root/children/40/children/2/children/0/children/2","type":"text","loc":{"start":7690,"end":7695,"line":{"s":158,"e":158,"code":["- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"]},"column":{"s":46,"e":51}},"dim":["","list.40","listItem.2","paragraph.0","text.2"],"code":" → N "},{"id":"/root/children/40/children/2/children/0/children/3","type":"inlineCode","loc":{"start":7695,"end":7705,"line":{"s":158,"e":158,"code":["- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"]},"column":{"s":51,"e":61}},"dim":["","list.40","listItem.2","paragraph.0","inlineCode.3"],"code":"`<option>`"},{"id":"/root/children/40/children/2/children/0/children/4","type":"text","loc":{"start":7705,"end":7714,"line":{"s":158,"e":158,"code":["- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements"]},"column":{"s":61,"e":70}},"dim":["","list.40","listItem.2","paragraph.0","text.4"],"code":" elements"},{"id":"/root/children/40/children/3","type":"listItem","loc":{"start":7715,"end":7788,"line":{"s":159,"e":159,"code":["- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"]},"column":{"s":0,"e":73}},"dim":["","list.40","listItem.3"],"code":"- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"},{"id":"/root/children/40/children/3/children/0","type":"paragraph","loc":{"start":7717,"end":7788,"line":{"s":159,"e":159,"code":["- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"]},"column":{"s":2,"e":73}},"dim":["","list.40","listItem.3","paragraph.0"],"code":"Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"},{"id":"/root/children/40/children/3/children/0/children/0","type":"text","loc":{"start":7717,"end":7730,"line":{"s":159,"e":159,"code":["- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"]},"column":{"s":2,"e":15}},"dim":["","list.40","listItem.3","paragraph.0","text.0"],"code":"Inline JSON: "},{"id":"/root/children/40/children/3/children/0/children/1","type":"inlineCode","loc":{"start":7730,"end":7764,"line":{"s":159,"e":159,"code":["- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"]},"column":{"s":15,"e":49}},"dim":["","list.40","listItem.3","paragraph.0","inlineCode.1"],"code":"`JSON.parse('¦.site.env|tojson¦')`"},{"id":"/root/children/40/children/3/children/0/children/2","type":"text","loc":{"start":7764,"end":7767,"line":{"s":159,"e":159,"code":["- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"]},"column":{"s":49,"e":52}},"dim":["","list.40","listItem.3","paragraph.0","text.2"],"code":" → "},{"id":"/root/children/40/children/3/children/0/children/3","type":"inlineCode","loc":{"start":7767,"end":7788,"line":{"s":159,"e":159,"code":["- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`"]},"column":{"s":52,"e":73}},"dim":["","list.40","listItem.3","paragraph.0","inlineCode.3"],"code":"`JSON.parse('\"loc\"')`"},{"id":"/root/children/41","type":"paragraph","loc":{"start":7790,"end":7824,"line":{"s":161,"e":161,"code":["**What passes through untouched:**"]},"column":{"s":0,"e":34}},"dim":["","paragraph.41"],"code":"**What passes through untouched:**"},{"id":"/root/children/41/children/0","type":"strong","loc":{"start":7790,"end":7824,"line":{"s":161,"e":161,"code":["**What passes through untouched:**"]},"column":{"s":0,"e":34}},"dim":["","paragraph.41","strong.0"],"code":"**What passes through untouched:**"},{"id":"/root/children/41/children/0/children/0","type":"text","loc":{"start":7792,"end":7822,"line":{"s":161,"e":161,"code":["**What passes through untouched:**"]},"column":{"s":2,"e":32}},"dim":["","paragraph.41","strong.0","text.0"],"code":"What passes through untouched:"},{"id":"/root/children/42","type":"list","loc":{"start":7826,"end":8063,"line":{"s":163,"e":165,"code":["- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)","- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`","- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":0,"e":65}},"dim":["","list.42"],"code":"- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`","symbName":"list","symbRange":[8065,23818],"symbRangeL":[163,444],"outerCode":"- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes","outerHtml":"<ul><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>"},{"id":"/root/children/42/children/0","type":"listItem","loc":{"start":7826,"end":7921,"line":{"s":163,"e":163,"code":["- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"]},"column":{"s":0,"e":95}},"dim":["","list.42","listItem.0"],"code":"- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"},{"id":"/root/children/42/children/0/children/0","type":"paragraph","loc":{"start":7828,"end":7921,"line":{"s":163,"e":163,"code":["- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"]},"column":{"s":2,"e":95}},"dim":["","list.42","listItem.0","paragraph.0"],"code":"`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"},{"id":"/root/children/42/children/0/children/0/children/0","type":"inlineCode","loc":{"start":7828,"end":7852,"line":{"s":163,"e":163,"code":["- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"]},"column":{"s":2,"e":26}},"dim":["","list.42","listItem.0","paragraph.0","inlineCode.0"],"code":"`<script type=\"module\">`"},{"id":"/root/children/42/children/0/children/0/children/1","type":"text","loc":{"start":7852,"end":7899,"line":{"s":163,"e":163,"code":["- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"]},"column":{"s":26,"e":73}},"dim":["","list.42","listItem.0","paragraph.0","text.1"],"code":" JS code (the jq-html engine only interpolates "},{"id":"/root/children/42/children/0/children/0/children/2","type":"inlineCode","loc":{"start":7899,"end":7903,"line":{"s":163,"e":163,"code":["- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"]},"column":{"s":73,"e":77}},"dim":["","list.42","listItem.0","paragraph.0","inlineCode.2"],"code":"`¦¦`"},{"id":"/root/children/42/children/0/children/0/children/3","type":"text","loc":{"start":7903,"end":7921,"line":{"s":163,"e":163,"code":["- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)"]},"column":{"s":77,"e":95}},"dim":["","list.42","listItem.0","paragraph.0","text.3"],"code":" tokens inside it)"},{"id":"/root/children/42/children/1","type":"listItem","loc":{"start":7922,"end":7997,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":0,"e":75}},"dim":["","list.42","listItem.1"],"code":"- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"},{"id":"/root/children/42/children/1/children/0","type":"paragraph","loc":{"start":7924,"end":7997,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":2,"e":75}},"dim":["","list.42","listItem.1","paragraph.0"],"code":"Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"},{"id":"/root/children/42/children/1/children/0/children/0","type":"text","loc":{"start":7924,"end":7942,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":2,"e":20}},"dim":["","list.42","listItem.1","paragraph.0","text.0"],"code":"Alpine.js syntax: "},{"id":"/root/children/42/children/1/children/0/children/1","type":"inlineCode","loc":{"start":7942,"end":7950,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":20,"e":28}},"dim":["","list.42","listItem.1","paragraph.0","inlineCode.1"],"code":"`x-data`"},{"id":"/root/children/42/children/1/children/0/children/2","type":"text","loc":{"start":7950,"end":7952,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":28,"e":30}},"dim":["","list.42","listItem.1","paragraph.0","text.2"],"code":", "},{"id":"/root/children/42/children/1/children/0/children/3","type":"inlineCode","loc":{"start":7952,"end":7959,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":30,"e":37}},"dim":["","list.42","listItem.1","paragraph.0","inlineCode.3"],"code":"`x-for`"},{"id":"/root/children/42/children/1/children/0/children/4","type":"text","loc":{"start":7959,"end":7961,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":37,"e":39}},"dim":["","list.42","listItem.1","paragraph.0","text.4"],"code":", "},{"id":"/root/children/42/children/1/children/0/children/5","type":"inlineCode","loc":{"start":7961,"end":7969,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":39,"e":47}},"dim":["","list.42","listItem.1","paragraph.0","inlineCode.5"],"code":"`x-show`"},{"id":"/root/children/42/children/1/children/0/children/6","type":"text","loc":{"start":7969,"end":7971,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":47,"e":49}},"dim":["","list.42","listItem.1","paragraph.0","text.6"],"code":", "},{"id":"/root/children/42/children/1/children/0/children/7","type":"inlineCode","loc":{"start":7971,"end":7979,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":49,"e":57}},"dim":["","list.42","listItem.1","paragraph.0","inlineCode.7"],"code":"`x-text`"},{"id":"/root/children/42/children/1/children/0/children/8","type":"text","loc":{"start":7979,"end":7981,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":57,"e":59}},"dim":["","list.42","listItem.1","paragraph.0","text.8"],"code":", "},{"id":"/root/children/42/children/1/children/0/children/9","type":"inlineCode","loc":{"start":7981,"end":7997,"line":{"s":164,"e":164,"code":["- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`"]},"column":{"s":59,"e":75}},"dim":["","list.42","listItem.1","paragraph.0","inlineCode.9"],"code":"`$store.glass.*`"},{"id":"/root/children/42/children/2","type":"listItem","loc":{"start":7998,"end":8063,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":0,"e":65}},"dim":["","list.42","listItem.2"],"code":"- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"},{"id":"/root/children/42/children/2/children/0","type":"paragraph","loc":{"start":8000,"end":8063,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":2,"e":65}},"dim":["","list.42","listItem.2","paragraph.0"],"code":"Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"},{"id":"/root/children/42/children/2/children/0/children/0","type":"text","loc":{"start":8000,"end":8020,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":2,"e":22}},"dim":["","list.42","listItem.2","paragraph.0","text.0"],"code":"Runtime attributes: "},{"id":"/root/children/42/children/2/children/0/children/1","type":"inlineCode","loc":{"start":8020,"end":8034,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":22,"e":36}},"dim":["","list.42","listItem.2","paragraph.0","inlineCode.1"],"code":"`resolveProps`"},{"id":"/root/children/42/children/2/children/0/children/2","type":"text","loc":{"start":8034,"end":8036,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":36,"e":38}},"dim":["","list.42","listItem.2","paragraph.0","text.2"],"code":", "},{"id":"/root/children/42/children/2/children/0/children/3","type":"inlineCode","loc":{"start":8036,"end":8048,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":38,"e":50}},"dim":["","list.42","listItem.2","paragraph.0","inlineCode.3"],"code":"`persistent`"},{"id":"/root/children/42/children/2/children/0/children/4","type":"text","loc":{"start":8048,"end":8050,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":50,"e":52}},"dim":["","list.42","listItem.2","paragraph.0","text.4"],"code":", "},{"id":"/root/children/42/children/2/children/0/children/5","type":"inlineCode","loc":{"start":8050,"end":8063,"line":{"s":165,"e":165,"code":["- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`"]},"column":{"s":52,"e":65}},"dim":["","list.42","listItem.2","paragraph.0","inlineCode.5"],"code":"`data-hotkey`"},{"id":"/root/children/43","type":"heading","loc":{"start":8065,"end":8086,"line":{"s":167,"e":167,"code":["## jq-html Templating"]},"column":{"s":0,"e":21}},"dim":["","heading.43"],"code":"## jq-html Templating","symbName":"heading","symbRange":[8088,9936],"symbRangeL":[167,199],"outerCode":"\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.","outerHtml":"\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>"},{"id":"/root/children/43/children/0","type":"text","loc":{"start":8068,"end":8086,"line":{"s":167,"e":167,"code":["## jq-html Templating"]},"column":{"s":3,"e":21}},"dim":["","heading.43","text.0"],"code":"jq-html Templating"},{"id":"/root/children/44","type":"paragraph","loc":{"start":8088,"end":8231,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":0,"e":143}},"dim":["","paragraph.44"],"code":"`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."},{"id":"/root/children/44/children/0","type":"inlineCode","loc":{"start":8088,"end":8102,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":0,"e":14}},"dim":["","paragraph.44","inlineCode.0"],"code":"`view.jq.html`"},{"id":"/root/children/44/children/1","type":"text","loc":{"start":8102,"end":8142,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":14,"e":54}},"dim":["","paragraph.44","text.1"],"code":" uses a custom templating engine called "},{"id":"/root/children/44/children/2","type":"strong","loc":{"start":8142,"end":8153,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":54,"e":65}},"dim":["","paragraph.44","strong.2"],"code":"**jq-html**"},{"id":"/root/children/44/children/2/children/0","type":"text","loc":{"start":8144,"end":8151,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":56,"e":63}},"dim":["","paragraph.44","strong.2","text.0"],"code":"jq-html"},{"id":"/root/children/44/children/3","type":"text","loc":{"start":8153,"end":8203,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":65,"e":115}},"dim":["","paragraph.44","text.3"],"code":". It embeds jq expressions directly in HTML using "},{"id":"/root/children/44/children/4","type":"inlineCode","loc":{"start":8203,"end":8206,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":115,"e":118}},"dim":["","paragraph.44","inlineCode.4"],"code":"`¦`"},{"id":"/root/children/44/children/5","type":"text","loc":{"start":8206,"end":8231,"line":{"s":169,"e":169,"code":["`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters."]},"column":{"s":118,"e":143}},"dim":["","paragraph.44","text.5"],"code":" (broken bar) delimiters."},{"id":"/root/children/45","type":"paragraph","loc":{"start":8233,"end":8249,"line":{"s":171,"e":171,"code":["**Three modes:**"]},"column":{"s":0,"e":16}},"dim":["","paragraph.45"],"code":"**Three modes:**"},{"id":"/root/children/45/children/0","type":"strong","loc":{"start":8233,"end":8249,"line":{"s":171,"e":171,"code":["**Three modes:**"]},"column":{"s":0,"e":16}},"dim":["","paragraph.45","strong.0"],"code":"**Three modes:**"},{"id":"/root/children/45/children/0/children/0","type":"text","loc":{"start":8235,"end":8247,"line":{"s":171,"e":171,"code":["**Three modes:**"]},"column":{"s":2,"e":14}},"dim":["","paragraph.45","strong.0","text.0"],"code":"Three modes:"},{"id":"/root/children/46","type":"paragraph","loc":{"start":8251,"end":8822,"line":{"s":173,"e":177,"code":["| Syntax                       | Where                               | Effect                         |","| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |","| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |","| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |","| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |"]},"column":{"s":0,"e":103}},"dim":["","paragraph.46"],"code":"| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |"},{"id":"/root/children/46/children/0","type":"text","loc":{"start":8251,"end":8487,"line":{"s":173,"e":175,"code":["| Syntax                       | Where                               | Effect                         |","| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |","| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.46","text.0"],"code":"| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| "},{"id":"/root/children/46/children/1","type":"inlineCode","loc":{"start":8487,"end":8495,"line":{"s":175,"e":175,"code":["| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.46","inlineCode.1"],"code":"`¦expr¦`"},{"id":"/root/children/46/children/2","type":"text","loc":{"start":8495,"end":8523,"line":{"s":175,"e":175,"code":["| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |"]},"column":{"s":10,"e":38}},"dim":["","paragraph.46","text.2"],"code":" in attribute value  | e.g. "},{"id":"/root/children/46/children/3","type":"inlineCode","loc":{"start":8523,"end":8564,"line":{"s":175,"e":175,"code":["| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |"]},"column":{"s":38,"e":79}},"dim":["","paragraph.46","inlineCode.3"],"code":"`src='¦\"assets://x\"            | s4url¦'`"},{"id":"/root/children/46/children/4","type":"text","loc":{"start":8564,"end":8617,"line":{"s":175,"e":176,"code":["| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |","| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |"]},"column":{"s":79,"e":2}},"dim":["","paragraph.46","text.4"],"code":"                       | Replaced with jq result |\n| "},{"id":"/root/children/46/children/5","type":"inlineCode","loc":{"start":8617,"end":8625,"line":{"s":176,"e":176,"code":["| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.46","inlineCode.5"],"code":"`¦expr¦`"},{"id":"/root/children/46/children/6","type":"text","loc":{"start":8625,"end":8653,"line":{"s":176,"e":176,"code":["| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |"]},"column":{"s":10,"e":38}},"dim":["","paragraph.46","text.6"],"code":" in text content     | e.g. "},{"id":"/root/children/46/children/7","type":"inlineCode","loc":{"start":8653,"end":8683,"line":{"s":176,"e":176,"code":["| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |"]},"column":{"s":38,"e":68}},"dim":["","paragraph.46","inlineCode.7"],"code":"`<title>¦.site.title¦</title>`"},{"id":"/root/children/46/children/8","type":"text","loc":{"start":8683,"end":8721,"line":{"s":176,"e":177,"code":["| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |","| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |"]},"column":{"s":68,"e":2}},"dim":["","paragraph.46","text.8"],"code":" | Replaced with jq result        |\n| "},{"id":"/root/children/46/children/9","type":"inlineCode","loc":{"start":8721,"end":8731,"line":{"s":177,"e":177,"code":["| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.46","inlineCode.9"],"code":"`¦=\"expr\"`"},{"id":"/root/children/46/children/10","type":"text","loc":{"start":8731,"end":8757,"line":{"s":177,"e":177,"code":["| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |"]},"column":{"s":12,"e":38}},"dim":["","paragraph.46","text.10"],"code":" as attribute name | e.g. "},{"id":"/root/children/46/children/11","type":"inlineCode","loc":{"start":8757,"end":8783,"line":{"s":177,"e":177,"code":["| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |"]},"column":{"s":38,"e":64}},"dim":["","paragraph.46","inlineCode.11"],"code":"`<option ¦=\".items\\|.[]\">`"},{"id":"/root/children/46/children/12","type":"text","loc":{"start":8783,"end":8822,"line":{"s":177,"e":177,"code":["| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |"]},"column":{"s":64,"e":103}},"dim":["","paragraph.46","text.12"],"code":"     | Element repeated per jq output |"},{"id":"/root/children/47","type":"paragraph","loc":{"start":8824,"end":9043,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":0,"e":219}},"dim":["","paragraph.47"],"code":"The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."},{"id":"/root/children/47/children/0","type":"text","loc":{"start":8824,"end":8843,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":0,"e":19}},"dim":["","paragraph.47","text.0"],"code":"The iteration form "},{"id":"/root/children/47/children/1","type":"inlineCode","loc":{"start":8843,"end":8853,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":19,"e":29}},"dim":["","paragraph.47","inlineCode.1"],"code":"`¦=\"expr\"`"},{"id":"/root/children/47/children/2","type":"text","loc":{"start":8853,"end":8946,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":29,"e":122}},"dim":["","paragraph.47","text.2"],"code":" clones the host element for each value the jq expression yields. Inside the cloned element, "},{"id":"/root/children/47/children/3","type":"inlineCode","loc":{"start":8946,"end":8949,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":122,"e":125}},"dim":["","paragraph.47","inlineCode.3"],"code":"`.`"},{"id":"/root/children/47/children/4","type":"text","loc":{"start":8949,"end":8987,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":125,"e":163}},"dim":["","paragraph.47","text.4"],"code":" refers to the current item. Example: "},{"id":"/root/children/47/children/5","type":"inlineCode","loc":{"start":8987,"end":9010,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":163,"e":186}},"dim":["","paragraph.47","inlineCode.5"],"code":"`<div ¦='[1,2,3]|.[]'>`"},{"id":"/root/children/47/children/6","type":"text","loc":{"start":9010,"end":9026,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":186,"e":202}},"dim":["","paragraph.47","text.6"],"code":" produces three "},{"id":"/root/children/47/children/7","type":"inlineCode","loc":{"start":9026,"end":9033,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":202,"e":209}},"dim":["","paragraph.47","inlineCode.7"],"code":"`<div>`"},{"id":"/root/children/47/children/8","type":"text","loc":{"start":9033,"end":9043,"line":{"s":179,"e":179,"code":["The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements."]},"column":{"s":209,"e":219}},"dim":["","paragraph.47","text.8"],"code":" elements."},{"id":"/root/children/48","type":"paragraph","loc":{"start":9045,"end":9085,"line":{"s":181,"e":181,"code":["**Common helpers used in the template:**"]},"column":{"s":0,"e":40}},"dim":["","paragraph.48"],"code":"**Common helpers used in the template:**"},{"id":"/root/children/48/children/0","type":"strong","loc":{"start":9045,"end":9085,"line":{"s":181,"e":181,"code":["**Common helpers used in the template:**"]},"column":{"s":0,"e":40}},"dim":["","paragraph.48","strong.0"],"code":"**Common helpers used in the template:**"},{"id":"/root/children/48/children/0/children/0","type":"text","loc":{"start":9047,"end":9083,"line":{"s":181,"e":181,"code":["**Common helpers used in the template:**"]},"column":{"s":2,"e":38}},"dim":["","paragraph.48","strong.0","text.0"],"code":"Common helpers used in the template:"},{"id":"/root/children/49","type":"paragraph","loc":{"start":9087,"end":9576,"line":{"s":183,"e":187,"code":["| Helper                     | Purpose                                                          |","| -------------------------- | ---------------------------------------------------------------- |","| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |","| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |","| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |"]},"column":{"s":0,"e":97}},"dim":["","paragraph.49"],"code":"| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |"},{"id":"/root/children/49/children/0","type":"text","loc":{"start":9087,"end":9285,"line":{"s":183,"e":185,"code":["| Helper                     | Purpose                                                          |","| -------------------------- | ---------------------------------------------------------------- |","| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.49","text.0"],"code":"| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| "},{"id":"/root/children/49/children/1","type":"inlineCode","loc":{"start":9285,"end":9292,"line":{"s":185,"e":185,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.49","inlineCode.1"],"code":"`s4url`"},{"id":"/root/children/49/children/2","type":"text","loc":{"start":9292,"end":9338,"line":{"s":185,"e":185,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":9,"e":55}},"dim":["","paragraph.49","text.2"],"code":"                    | Resolves protocol URIs ("},{"id":"/root/children/49/children/3","type":"inlineCode","loc":{"start":9338,"end":9349,"line":{"s":185,"e":185,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":55,"e":66}},"dim":["","paragraph.49","inlineCode.3"],"code":"`assets://`"},{"id":"/root/children/49/children/4","type":"text","loc":{"start":9349,"end":9351,"line":{"s":185,"e":185,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":66,"e":68}},"dim":["","paragraph.49","text.4"],"code":", "},{"id":"/root/children/49/children/5","type":"inlineCode","loc":{"start":9351,"end":9360,"line":{"s":185,"e":185,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":68,"e":77}},"dim":["","paragraph.49","inlineCode.5"],"code":"`repo://`"},{"id":"/root/children/49/children/6","type":"text","loc":{"start":9360,"end":9362,"line":{"s":185,"e":185,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":77,"e":79}},"dim":["","paragraph.49","text.6"],"code":", "},{"id":"/root/children/49/children/7","type":"inlineCode","loc":{"start":9362,"end":9371,"line":{"s":185,"e":185,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |"]},"column":{"s":79,"e":88}},"dim":["","paragraph.49","inlineCode.7"],"code":"`crud://`"},{"id":"/root/children/49/children/8","type":"text","loc":{"start":9371,"end":9383,"line":{"s":185,"e":186,"code":["| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |","| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |"]},"column":{"s":88,"e":2}},"dim":["","paragraph.49","text.8"],"code":", etc.) |\n| "},{"id":"/root/children/49/children/9","type":"inlineCode","loc":{"start":9383,"end":9409,"line":{"s":186,"e":186,"code":["| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |"]},"column":{"s":2,"e":28}},"dim":["","paragraph.49","inlineCode.9"],"code":"`s4FilterIncludes($input)`"},{"id":"/root/children/49/children/10","type":"text","loc":{"start":9409,"end":9481,"line":{"s":186,"e":187,"code":["| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |","| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |"]},"column":{"s":28,"e":2}},"dim":["","paragraph.49","text.10"],"code":" | Filters theme config arrays by inclusion rules                   |\n| "},{"id":"/root/children/49/children/11","type":"inlineCode","loc":{"start":9481,"end":9489,"line":{"s":187,"e":187,"code":["| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.49","inlineCode.11"],"code":"`tojson`"},{"id":"/root/children/49/children/12","type":"text","loc":{"start":9489,"end":9576,"line":{"s":187,"e":187,"code":["| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |"]},"column":{"s":10,"e":97}},"dim":["","paragraph.49","text.12"],"code":"                   | Wraps result in JSON for safe embedding into JS vars             |"},{"id":"/root/children/50","type":"paragraph","loc":{"start":9578,"end":9640,"line":{"s":189,"e":189,"code":["**How the template feeds player.js** (in the boot `<script>`):"]},"column":{"s":0,"e":62}},"dim":["","paragraph.50"],"code":"**How the template feeds player.js** (in the boot `<script>`):"},{"id":"/root/children/50/children/0","type":"strong","loc":{"start":9578,"end":9614,"line":{"s":189,"e":189,"code":["**How the template feeds player.js** (in the boot `<script>`):"]},"column":{"s":0,"e":36}},"dim":["","paragraph.50","strong.0"],"code":"**How the template feeds player.js**"},{"id":"/root/children/50/children/0/children/0","type":"text","loc":{"start":9580,"end":9612,"line":{"s":189,"e":189,"code":["**How the template feeds player.js** (in the boot `<script>`):"]},"column":{"s":2,"e":34}},"dim":["","paragraph.50","strong.0","text.0"],"code":"How the template feeds player.js"},{"id":"/root/children/50/children/1","type":"text","loc":{"start":9614,"end":9628,"line":{"s":189,"e":189,"code":["**How the template feeds player.js** (in the boot `<script>`):"]},"column":{"s":36,"e":50}},"dim":["","paragraph.50","text.1"],"code":" (in the boot "},{"id":"/root/children/50/children/2","type":"inlineCode","loc":{"start":9628,"end":9638,"line":{"s":189,"e":189,"code":["**How the template feeds player.js** (in the boot `<script>`):"]},"column":{"s":50,"e":60}},"dim":["","paragraph.50","inlineCode.2"],"code":"`<script>`"},{"id":"/root/children/50/children/3","type":"text","loc":{"start":9638,"end":9640,"line":{"s":189,"e":189,"code":["**How the template feeds player.js** (in the boot `<script>`):"]},"column":{"s":60,"e":62}},"dim":["","paragraph.50","text.3"],"code":"):"},{"id":"/root/children/51","type":"code","loc":{"start":9642,"end":9811,"line":{"s":191,"e":196,"code":["```","const env = JSON.parse('¦.site.env|tojson¦')","const siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')","const mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')","...","```"]},"column":{"s":0,"e":3}},"dim":["","code.51"],"code":"```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```","symbName":"code","symbRange":[9813,10084],"symbRangeL":[null,205],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>"},{"id":"/root/children/52","type":"paragraph","loc":{"start":9813,"end":9936,"line":{"s":198,"e":198,"code":["Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`."]},"column":{"s":0,"e":123}},"dim":["","paragraph.52"],"code":"Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`."},{"id":"/root/children/52/children/0","type":"text","loc":{"start":9813,"end":9920,"line":{"s":198,"e":198,"code":["Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`."]},"column":{"s":0,"e":107}},"dim":["","paragraph.52","text.0"],"code":"Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into "},{"id":"/root/children/52/children/1","type":"inlineCode","loc":{"start":9920,"end":9935,"line":{"s":198,"e":198,"code":["Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`."]},"column":{"s":107,"e":122}},"dim":["","paragraph.52","inlineCode.1"],"code":"`player({...})`"},{"id":"/root/children/52/children/2","type":"text","loc":{"start":9935,"end":9936,"line":{"s":198,"e":198,"code":["Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`."]},"column":{"s":122,"e":123}},"dim":["","paragraph.52","text.2"],"code":"."},{"id":"/root/children/53","type":"heading","loc":{"start":9938,"end":9974,"line":{"s":200,"e":200,"code":["## How It Works (HTML + JS together)"]},"column":{"s":0,"e":36}},"dim":["","heading.53"],"code":"## How It Works (HTML + JS together)","symbName":"heading","symbRange":[9976,70981],"symbRangeL":[200,201],"outerCode":"","outerHtml":""},{"id":"/root/children/53/children/0","type":"text","loc":{"start":9941,"end":9974,"line":{"s":200,"e":200,"code":["## How It Works (HTML + JS together)"]},"column":{"s":3,"e":36}},"dim":["","heading.53","text.0"],"code":"How It Works (HTML + JS together)"},{"id":"/root/children/54","type":"heading","loc":{"start":9976,"end":10011,"line":{"s":202,"e":202,"code":["### The DOM Skeleton (view.jq.html)"]},"column":{"s":0,"e":35}},"dim":["","heading.54"],"code":"### The DOM Skeleton (view.jq.html)","symbName":"heading","symbRange":[10013,10739],"symbRangeL":[202,220],"outerCode":"\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.","outerHtml":"\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>"},{"id":"/root/children/54/children/0","type":"text","loc":{"start":9980,"end":10011,"line":{"s":202,"e":202,"code":["### The DOM Skeleton (view.jq.html)"]},"column":{"s":4,"e":35}},"dim":["","heading.54","text.0"],"code":"The DOM Skeleton (view.jq.html)"},{"id":"/root/children/55","type":"paragraph","loc":{"start":10013,"end":10084,"line":{"s":204,"e":204,"code":["The HTML defines five top-level containers, only one visible at a time:"]},"column":{"s":0,"e":71}},"dim":["","paragraph.55"],"code":"The HTML defines five top-level containers, only one visible at a time:"},{"id":"/root/children/55/children/0","type":"text","loc":{"start":10013,"end":10084,"line":{"s":204,"e":204,"code":["The HTML defines five top-level containers, only one visible at a time:"]},"column":{"s":0,"e":71}},"dim":["","paragraph.55","text.0"],"code":"The HTML defines five top-level containers, only one visible at a time:"},{"id":"/root/children/56","type":"code","loc":{"start":10086,"end":10670,"line":{"s":206,"e":217,"code":["```","<body class=\"spa frozen\">","  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)","  #space > #map    OpenLayers map container (starts .off)","  #paper           empty div, filled dynamically (starts .on)","  #edit > #editor  CodeMirror textarea (starts .off)","  #glass           search/command overlay (starts .off)","    .menu-navigator    nav buttons: edit, space, glass-toggle","    .menu-content      sections: filters, search, actions, history, options, .secret, console, links","  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)","</body>","```"]},"column":{"s":0,"e":3}},"dim":["","code.56"],"code":"```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```","symbName":"code","symbRange":[10672,10758],"symbRangeL":[null,222],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>"},{"id":"/root/children/57","type":"paragraph","loc":{"start":10672,"end":10739,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":0,"e":67}},"dim":["","paragraph.57"],"code":"Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."},{"id":"/root/children/57/children/0","type":"text","loc":{"start":10672,"end":10695,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":0,"e":23}},"dim":["","paragraph.57","text.0"],"code":"Layers are toggled via "},{"id":"/root/children/57/children/1","type":"inlineCode","loc":{"start":10695,"end":10700,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":23,"e":28}},"dim":["","paragraph.57","inlineCode.1"],"code":"`.on`"},{"id":"/root/children/57/children/2","type":"text","loc":{"start":10700,"end":10703,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":28,"e":31}},"dim":["","paragraph.57","text.2"],"code":" / "},{"id":"/root/children/57/children/3","type":"inlineCode","loc":{"start":10703,"end":10709,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":31,"e":37}},"dim":["","paragraph.57","inlineCode.3"],"code":"`.off`"},{"id":"/root/children/57/children/4","type":"text","loc":{"start":10709,"end":10725,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":37,"e":53}},"dim":["","paragraph.57","text.4"],"code":" CSS classes by "},{"id":"/root/children/57/children/5","type":"inlineCode","loc":{"start":10725,"end":10738,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":53,"e":66}},"dim":["","paragraph.57","inlineCode.5"],"code":"`eSetOnOff()`"},{"id":"/root/children/57/children/6","type":"text","loc":{"start":10738,"end":10739,"line":{"s":219,"e":219,"code":["Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`."]},"column":{"s":66,"e":67}},"dim":["","paragraph.57","text.6"],"code":"."},{"id":"/root/children/58","type":"heading","loc":{"start":10741,"end":10758,"line":{"s":221,"e":221,"code":["### Boot Sequence"]},"column":{"s":0,"e":17}},"dim":["","heading.58"],"code":"### Boot Sequence","symbName":"heading","symbRange":[10760,11128],"symbRangeL":[221,231],"outerCode":"\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```","outerHtml":"\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>"},{"id":"/root/children/58/children/0","type":"text","loc":{"start":10745,"end":10758,"line":{"s":221,"e":221,"code":["### Boot Sequence"]},"column":{"s":4,"e":17}},"dim":["","heading.58","text.0"],"code":"Boot Sequence"},{"id":"/root/children/59","type":"code","loc":{"start":10760,"end":11128,"line":{"s":223,"e":230,"code":["```","view.jq.html <script type=\"module\">","  1. jq-html templates inject server config into JS vars","     (env, siteVer, siteHotkey, all URL bases, protocolRouter)","  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh","  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout","  4. player({ssss, ...all config...})        -> boots the app","```"]},"column":{"s":0,"e":3}},"dim":["","code.59"],"code":"```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```","symbName":"code","symbRange":[11130,11149],"symbRangeL":[null,233],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>"},{"id":"/root/children/60","type":"heading","loc":{"start":11130,"end":11149,"line":{"s":232,"e":232,"code":["### Inside player()"]},"column":{"s":0,"e":19}},"dim":["","heading.60"],"code":"### Inside player()","symbName":"heading","symbRange":[11151,12197],"symbRangeL":[232,257],"outerCode":"\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```","outerHtml":"\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>"},{"id":"/root/children/60/children/0","type":"text","loc":{"start":11134,"end":11149,"line":{"s":232,"e":232,"code":["### Inside player()"]},"column":{"s":4,"e":19}},"dim":["","heading.60","text.0"],"code":"Inside player()"},{"id":"/root/children/61","type":"code","loc":{"start":11151,"end":12197,"line":{"s":234,"e":256,"code":["```","player()","  |  STATE init: repoName, history, indexJson, ast, last-hashes","  |  Notyf init (toaster)","  |  PROXY assembly (shared deps bag)","  |","  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)","  |     |","   |     |-- bootS4callback() runs once:","   |     |     - resetMachine() -> fetch .repo.json, init space map","   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list","   |     |     - initGlassStatus() -> Alpine store on #status","   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys","  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)","  |     |     - persistent elements: restore checkboxes/details from localStorage","  |     |     - global hotkeys via keymage","  |     |     - window.* function assignments","  |     |","  |     |-- routeFallback(tokens) runs on every hash change:","  |           parses: #/{layer}/{...path}[@command]","  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)","  |           stores: history, last route","```"]},"column":{"s":0,"e":3}},"dim":["","code.61"],"code":"```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```","symbName":"code","symbRange":[12199,12305],"symbRangeL":[null,261],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>"},{"id":"/root/children/62","type":"heading","loc":{"start":12199,"end":12236,"line":{"s":258,"e":258,"code":["### renderLayerMode - the core switch"]},"column":{"s":0,"e":37}},"dim":["","heading.62"],"code":"### renderLayerMode - the core switch","symbName":"heading","symbRange":[12238,14221],"symbRangeL":[258,311],"outerCode":"\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```","outerHtml":"\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>"},{"id":"/root/children/62/children/0","type":"text","loc":{"start":12203,"end":12236,"line":{"s":258,"e":258,"code":["### renderLayerMode - the core switch"]},"column":{"s":4,"e":37}},"dim":["","heading.62","text.0"],"code":"renderLayerMode - the core switch"},{"id":"/root/children/63","type":"paragraph","loc":{"start":12238,"end":12305,"line":{"s":260,"e":260,"code":["This is the heart. It receives a `targetLayer` and toggles the DOM:"]},"column":{"s":0,"e":67}},"dim":["","paragraph.63"],"code":"This is the heart. It receives a `targetLayer` and toggles the DOM:"},{"id":"/root/children/63/children/0","type":"text","loc":{"start":12238,"end":12271,"line":{"s":260,"e":260,"code":["This is the heart. It receives a `targetLayer` and toggles the DOM:"]},"column":{"s":0,"e":33}},"dim":["","paragraph.63","text.0"],"code":"This is the heart. It receives a "},{"id":"/root/children/63/children/1","type":"inlineCode","loc":{"start":12271,"end":12284,"line":{"s":260,"e":260,"code":["This is the heart. It receives a `targetLayer` and toggles the DOM:"]},"column":{"s":33,"e":46}},"dim":["","paragraph.63","inlineCode.1"],"code":"`targetLayer`"},{"id":"/root/children/63/children/2","type":"text","loc":{"start":12284,"end":12305,"line":{"s":260,"e":260,"code":["This is the heart. It receives a `targetLayer` and toggles the DOM:"]},"column":{"s":46,"e":67}},"dim":["","paragraph.63","text.2"],"code":" and toggles the DOM:"},{"id":"/root/children/64","type":"code","loc":{"start":12307,"end":14221,"line":{"s":262,"e":310,"code":["```","renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)","  |","  |-- unbind previous hotkeys (trapsLayer cleanup)","  |-- set body[data-mode] = targetLayer","  |","  |-- switch(targetLayer):","  |","  |   'glass':","  |     show #glass, show #portal.console","   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)","  |     focusMenuInput() -> apply armed search value, focus #menu-input","  |     if @query command: arm search input with query string","  |     bind hotkeys for #glass + #main-menu","  |","  |   'paper':","  |     show #paper, hide #glass/#edit/#space","  |     update #e-main -> links to #/space","  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)","  |     if @command (dashboard, denied, login, demo):","  |       getCommandHandler() or dashboard() renders into #paper","  |       reloadCurrentPaper = the reload function (kept for MQ reload)","  |     else (normal file):","  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper","  |       tabler() adds heatmaps to tables","  |       observeSubTree() tracks visible headings for status bar","  |     bind hotkeys for #paper","  |","  |   'edit':","  |     show #edit, hide others","  |     update #e-secondary -> \"save\" link to #/save/paper/{file}","  |     fetch file content, initEditor() -> CodeMirror 6 in #editor","  |     restore cursor position from localStorage","  |     wire editor events: save, autocomplete, inspect, larry macros","  |     bind hotkeys for #edit","  |","  |   'space':","  |     show #space, hide others","  |     if no tokens: restore last space hash from history","  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified","  |     bind hotkeys for #space","  |","  |   'save':","  |     savePaper() -> editSaveWithWebhook() via POST to webhook","  |     on success: navigate back to #/paper/{file}","  |","  |-- initToolTipBar() -> re-render toolbar tooltips","  |-- focus the new layer element","```"]},"column":{"s":0,"e":3}},"dim":["","code.64"],"code":"```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```","symbName":"code","symbRange":[14223,16152],"symbRangeL":[null,333],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>"},{"id":"/root/children/65","type":"heading","loc":{"start":14223,"end":14275,"line":{"s":312,"e":312,"code":["### How the HTML sections map to player.js functions"]},"column":{"s":0,"e":52}},"dim":["","heading.65"],"code":"### How the HTML sections map to player.js functions","symbName":"heading","symbRange":[14277,16129],"symbRangeL":[312,331],"outerCode":"\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |","outerHtml":"\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>"},{"id":"/root/children/65/children/0","type":"text","loc":{"start":14227,"end":14275,"line":{"s":312,"e":312,"code":["### How the HTML sections map to player.js functions"]},"column":{"s":4,"e":52}},"dim":["","heading.65","text.0"],"code":"How the HTML sections map to player.js functions"},{"id":"/root/children/66","type":"paragraph","loc":{"start":14277,"end":16129,"line":{"s":314,"e":330,"code":["| HTML element              | player.js handler                                                            |","| ------------------------- | ---------------------------------------------------------------------------- |","| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |","| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |","| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |","| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |","| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |","| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |","| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |","| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |","| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |","| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |","| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |","| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |","| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |","| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |","| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":0,"e":108}},"dim":["","paragraph.66"],"code":"| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"},{"id":"/root/children/66/children/0","type":"text","loc":{"start":14277,"end":14497,"line":{"s":314,"e":316,"code":["| HTML element              | player.js handler                                                            |","| ------------------------- | ---------------------------------------------------------------------------- |","| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.66","text.0"],"code":"| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| "},{"id":"/root/children/66/children/1","type":"inlineCode","loc":{"start":14497,"end":14510,"line":{"s":316,"e":316,"code":["| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |"]},"column":{"s":2,"e":15}},"dim":["","paragraph.66","inlineCode.1"],"code":"`#menu-input`"},{"id":"/root/children/66/children/2","type":"text","loc":{"start":14510,"end":14525,"line":{"s":316,"e":316,"code":["| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |"]},"column":{"s":15,"e":30}},"dim":["","paragraph.66","text.2"],"code":"             | "},{"id":"/root/children/66/children/3","type":"inlineCode","loc":{"start":14525,"end":14555,"line":{"s":316,"e":316,"code":["| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |"]},"column":{"s":30,"e":60}},"dim":["","paragraph.66","inlineCode.3"],"code":"`initGlassAutocompleteInput()`"},{"id":"/root/children/66/children/4","type":"text","loc":{"start":14555,"end":14579,"line":{"s":316,"e":316,"code":["| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |"]},"column":{"s":60,"e":84}},"dim":["","paragraph.66","text.4"],"code":" -> Autocomplete lib -> "},{"id":"/root/children/66/children/5","type":"inlineCode","loc":{"start":14579,"end":14597,"line":{"s":316,"e":316,"code":["| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |"]},"column":{"s":84,"e":102}},"dim":["","paragraph.66","inlineCode.5"],"code":"`glassSearchRun()`"},{"id":"/root/children/66/children/6","type":"text","loc":{"start":14597,"end":14606,"line":{"s":316,"e":317,"code":["| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |","| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |"]},"column":{"s":102,"e":2}},"dim":["","paragraph.66","text.6"],"code":"     |\n| "},{"id":"/root/children/66/children/7","type":"inlineCode","loc":{"start":14606,"end":14618,"line":{"s":317,"e":317,"code":["| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |"]},"column":{"s":2,"e":14}},"dim":["","paragraph.66","inlineCode.7"],"code":"`#menu-list`"},{"id":"/root/children/66/children/8","type":"text","loc":{"start":14618,"end":14634,"line":{"s":317,"e":317,"code":["| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |"]},"column":{"s":14,"e":30}},"dim":["","paragraph.66","text.8"],"code":" (slot)       | "},{"id":"/root/children/66/children/9","type":"inlineCode","loc":{"start":14634,"end":14651,"line":{"s":317,"e":317,"code":["| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |"]},"column":{"s":30,"e":47}},"dim":["","paragraph.66","inlineCode.9"],"code":"`initGlassMenu()`"},{"id":"/root/children/66/children/10","type":"text","loc":{"start":14651,"end":14715,"line":{"s":317,"e":318,"code":["| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |","| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |"]},"column":{"s":47,"e":2}},"dim":["","paragraph.66","text.10"],"code":" -> Alpine store + template with history, papers, fragments |\n| "},{"id":"/root/children/66/children/11","type":"inlineCode","loc":{"start":14715,"end":14724,"line":{"s":318,"e":318,"code":["| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.66","inlineCode.11"],"code":"`#status`"},{"id":"/root/children/66/children/12","type":"text","loc":{"start":14724,"end":14743,"line":{"s":318,"e":318,"code":["| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |"]},"column":{"s":11,"e":30}},"dim":["","paragraph.66","text.12"],"code":" (slot)          | "},{"id":"/root/children/66/children/13","type":"inlineCode","loc":{"start":14743,"end":14762,"line":{"s":318,"e":318,"code":["| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |"]},"column":{"s":30,"e":49}},"dim":["","paragraph.66","inlineCode.13"],"code":"`initGlassStatus()`"},{"id":"/root/children/66/children/14","type":"text","loc":{"start":14762,"end":14824,"line":{"s":318,"e":319,"code":["| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |","| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |"]},"column":{"s":49,"e":2}},"dim":["","paragraph.66","text.14"],"code":" -> Alpine store with statusTL/statusBL                   |\n| "},{"id":"/root/children/66/children/15","type":"inlineCode","loc":{"start":14824,"end":14842,"line":{"s":319,"e":319,"code":["| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |"]},"column":{"s":2,"e":20}},"dim":["","paragraph.66","inlineCode.15"],"code":"`#alpine-touchbar`"},{"id":"/root/children/66/children/16","type":"text","loc":{"start":14842,"end":14852,"line":{"s":319,"e":319,"code":["| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |"]},"column":{"s":20,"e":30}},"dim":["","paragraph.66","text.16"],"code":"        | "},{"id":"/root/children/66/children/17","type":"inlineCode","loc":{"start":14852,"end":14870,"line":{"s":319,"e":319,"code":["| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |"]},"column":{"s":30,"e":48}},"dim":["","paragraph.66","inlineCode.17"],"code":"`initToolTipBar()`"},{"id":"/root/children/66/children/18","type":"text","loc":{"start":14870,"end":14933,"line":{"s":319,"e":320,"code":["| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |","| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |"]},"column":{"s":48,"e":2}},"dim":["","paragraph.66","text.18"],"code":" -> Alpine x-for toolbar with icon buttons                 |\n| "},{"id":"/root/children/66/children/19","type":"inlineCode","loc":{"start":14933,"end":14941,"line":{"s":320,"e":320,"code":["| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.66","inlineCode.19"],"code":"`#paper`"},{"id":"/root/children/66/children/20","type":"text","loc":{"start":14941,"end":14961,"line":{"s":320,"e":320,"code":["| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |"]},"column":{"s":10,"e":30}},"dim":["","paragraph.66","text.20"],"code":"                  | "},{"id":"/root/children/66/children/21","type":"inlineCode","loc":{"start":14961,"end":14987,"line":{"s":320,"e":320,"code":["| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |"]},"column":{"s":30,"e":56}},"dim":["","paragraph.66","inlineCode.21"],"code":"`renderLayerMode('paper')`"},{"id":"/root/children/66/children/22","type":"text","loc":{"start":14987,"end":15042,"line":{"s":320,"e":321,"code":["| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |","| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |"]},"column":{"s":56,"e":2}},"dim":["","paragraph.66","text.22"],"code":" fills innerHTML (mdd/html/dashboard)              |\n| "},{"id":"/root/children/66/children/23","type":"inlineCode","loc":{"start":15042,"end":15051,"line":{"s":321,"e":321,"code":["| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.66","inlineCode.23"],"code":"`#editor`"},{"id":"/root/children/66/children/24","type":"text","loc":{"start":15051,"end":15070,"line":{"s":321,"e":321,"code":["| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |"]},"column":{"s":11,"e":30}},"dim":["","paragraph.66","text.24"],"code":"                 | "},{"id":"/root/children/66/children/25","type":"inlineCode","loc":{"start":15070,"end":15095,"line":{"s":321,"e":321,"code":["| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |"]},"column":{"s":30,"e":55}},"dim":["","paragraph.66","inlineCode.25"],"code":"`renderLayerMode('edit')`"},{"id":"/root/children/66/children/26","type":"text","loc":{"start":15095,"end":15099,"line":{"s":321,"e":321,"code":["| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |"]},"column":{"s":55,"e":59}},"dim":["","paragraph.66","text.26"],"code":" -> "},{"id":"/root/children/66/children/27","type":"inlineCode","loc":{"start":15099,"end":15113,"line":{"s":321,"e":321,"code":["| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |"]},"column":{"s":59,"e":73}},"dim":["","paragraph.66","inlineCode.27"],"code":"`initEditor()`"},{"id":"/root/children/66/children/28","type":"text","loc":{"start":15113,"end":15151,"line":{"s":321,"e":322,"code":["| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |","| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |"]},"column":{"s":73,"e":2}},"dim":["","paragraph.66","text.28"],"code":" -> CodeMirror 6                  |\n| "},{"id":"/root/children/66/children/29","type":"inlineCode","loc":{"start":15151,"end":15157,"line":{"s":322,"e":322,"code":["| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.66","inlineCode.29"],"code":"`#map`"},{"id":"/root/children/66/children/30","type":"text","loc":{"start":15157,"end":15179,"line":{"s":322,"e":322,"code":["| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |"]},"column":{"s":8,"e":30}},"dim":["","paragraph.66","text.30"],"code":"                    | "},{"id":"/root/children/66/children/31","type":"inlineCode","loc":{"start":15179,"end":15193,"line":{"s":322,"e":322,"code":["| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |"]},"column":{"s":30,"e":44}},"dim":["","paragraph.66","inlineCode.31"],"code":"`resetSpace()`"},{"id":"/root/children/66/children/32","type":"text","loc":{"start":15193,"end":15197,"line":{"s":322,"e":322,"code":["| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |"]},"column":{"s":44,"e":48}},"dim":["","paragraph.66","text.32"],"code":" -> "},{"id":"/root/children/66/children/33","type":"inlineCode","loc":{"start":15197,"end":15208,"line":{"s":322,"e":322,"code":["| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |"]},"column":{"s":48,"e":59}},"dim":["","paragraph.66","inlineCode.33"],"code":"`mapShow()`"},{"id":"/root/children/66/children/34","type":"text","loc":{"start":15208,"end":15260,"line":{"s":322,"e":323,"code":["| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |","| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |"]},"column":{"s":59,"e":2}},"dim":["","paragraph.66","text.34"],"code":" -> OpenLayers in #map                          |\n| "},{"id":"/root/children/66/children/35","type":"inlineCode","loc":{"start":15260,"end":15269,"line":{"s":323,"e":323,"code":["| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.66","inlineCode.35"],"code":"`#e-main`"},{"id":"/root/children/66/children/36","type":"text","loc":{"start":15269,"end":15369,"line":{"s":323,"e":324,"code":["| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |","| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |"]},"column":{"s":11,"e":2}},"dim":["","paragraph.66","text.36"],"code":"                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| "},{"id":"/root/children/66/children/37","type":"inlineCode","loc":{"start":15369,"end":15383,"line":{"s":324,"e":324,"code":["| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.66","inlineCode.37"],"code":"`#e-secondary`"},{"id":"/root/children/66/children/38","type":"text","loc":{"start":15383,"end":15478,"line":{"s":324,"e":325,"code":["| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |","| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |"]},"column":{"s":16,"e":2}},"dim":["","paragraph.66","text.38"],"code":"            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| "},{"id":"/root/children/66/children/39","type":"inlineCode","loc":{"start":15478,"end":15492,"line":{"s":325,"e":325,"code":["| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.66","inlineCode.39"],"code":"`[persistent]`"},{"id":"/root/children/66/children/40","type":"text","loc":{"start":15492,"end":15587,"line":{"s":325,"e":326,"code":["| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |","| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |"]},"column":{"s":16,"e":2}},"dim":["","paragraph.66","text.40"],"code":" elements   | event handler restores/saves checkbox/details state via localStorage         |\n| "},{"id":"/root/children/66/children/41","type":"inlineCode","loc":{"start":15587,"end":15602,"line":{"s":326,"e":326,"code":["| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |"]},"column":{"s":2,"e":17}},"dim":["","paragraph.66","inlineCode.41"],"code":"`[data-hotkey]`"},{"id":"/root/children/66/children/42","type":"text","loc":{"start":15602,"end":15615,"line":{"s":326,"e":326,"code":["| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |"]},"column":{"s":17,"e":30}},"dim":["","paragraph.66","text.42"],"code":" elements  | "},{"id":"/root/children/66/children/43","type":"inlineCode","loc":{"start":15615,"end":15632,"line":{"s":326,"e":326,"code":["| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |"]},"column":{"s":30,"e":47}},"dim":["","paragraph.66","inlineCode.43"],"code":"`injestHotkeys()`"},{"id":"/root/children/66/children/44","type":"text","loc":{"start":15632,"end":15696,"line":{"s":326,"e":327,"code":["| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |","| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |"]},"column":{"s":47,"e":2}},"dim":["","paragraph.66","text.44"],"code":" scans + binds via keymage per layer                        |\n| "},{"id":"/root/children/66/children/45","type":"inlineCode","loc":{"start":15696,"end":15712,"line":{"s":327,"e":327,"code":["| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |"]},"column":{"s":2,"e":18}},"dim":["","paragraph.66","inlineCode.45"],"code":"`[data-tooltip]`"},{"id":"/root/children/66/children/46","type":"text","loc":{"start":15712,"end":15724,"line":{"s":327,"e":327,"code":["| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |"]},"column":{"s":18,"e":30}},"dim":["","paragraph.66","text.46"],"code":" elements | "},{"id":"/root/children/66/children/47","type":"inlineCode","loc":{"start":15724,"end":15742,"line":{"s":327,"e":327,"code":["| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |"]},"column":{"s":30,"e":48}},"dim":["","paragraph.66","inlineCode.47"],"code":"`initToolTipBar()`"},{"id":"/root/children/66/children/48","type":"text","loc":{"start":15742,"end":15805,"line":{"s":327,"e":328,"code":["| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |","| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |"]},"column":{"s":48,"e":2}},"dim":["","paragraph.66","text.48"],"code":" -> touch/hover tooltip rendering                          |\n| "},{"id":"/root/children/66/children/49","type":"inlineCode","loc":{"start":15805,"end":15821,"line":{"s":328,"e":328,"code":["| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |"]},"column":{"s":2,"e":18}},"dim":["","paragraph.66","inlineCode.49"],"code":"`[resolveProps]`"},{"id":"/root/children/66/children/50","type":"text","loc":{"start":15821,"end":15833,"line":{"s":328,"e":328,"code":["| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |"]},"column":{"s":18,"e":30}},"dim":["","paragraph.66","text.50"],"code":" elements | "},{"id":"/root/children/66/children/51","type":"inlineCode","loc":{"start":15833,"end":15857,"line":{"s":328,"e":328,"code":["| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |"]},"column":{"s":30,"e":54}},"dim":["","paragraph.66","inlineCode.51"],"code":"`addOneTimeTweaksTodo()`"},{"id":"/root/children/66/children/52","type":"text","loc":{"start":15857,"end":15914,"line":{"s":328,"e":329,"code":["| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |","| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |"]},"column":{"s":54,"e":2}},"dim":["","paragraph.66","text.52"],"code":" patches href/value/text with runtime vars           |\n| "},{"id":"/root/children/66/children/53","type":"inlineCode","loc":{"start":15914,"end":15933,"line":{"s":329,"e":329,"code":["| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |"]},"column":{"s":2,"e":21}},"dim":["","paragraph.66","inlineCode.53"],"code":"`onclick=emitRepo*`"},{"id":"/root/children/66/children/54","type":"text","loc":{"start":15933,"end":15942,"line":{"s":329,"e":329,"code":["| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |"]},"column":{"s":21,"e":30}},"dim":["","paragraph.66","text.54"],"code":"       | "},{"id":"/root/children/66/children/55","type":"inlineCode","loc":{"start":15942,"end":15984,"line":{"s":329,"e":329,"code":["| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |"]},"column":{"s":30,"e":72}},"dim":["","paragraph.66","inlineCode.55"],"code":"`window.emitRepoActualize/Index/Sync/Mail`"},{"id":"/root/children/66/children/56","type":"text","loc":{"start":15984,"end":15988,"line":{"s":329,"e":329,"code":["| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |"]},"column":{"s":72,"e":76}},"dim":["","paragraph.66","text.56"],"code":" -> "},{"id":"/root/children/66/children/57","type":"inlineCode","loc":{"start":15988,"end":16005,"line":{"s":329,"e":329,"code":["| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |"]},"column":{"s":76,"e":93}},"dim":["","paragraph.66","inlineCode.57"],"code":"`emitMqMessage()`"},{"id":"/root/children/66/children/58","type":"text","loc":{"start":16005,"end":16023,"line":{"s":329,"e":330,"code":["| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |","| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":93,"e":2}},"dim":["","paragraph.66","text.58"],"code":"              |\n| "},{"id":"/root/children/66/children/59","type":"inlineCode","loc":{"start":16023,"end":16043,"line":{"s":330,"e":330,"code":["| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":2,"e":22}},"dim":["","paragraph.66","inlineCode.59"],"code":"`onclick=closeGlass`"},{"id":"/root/children/66/children/60","type":"text","loc":{"start":16043,"end":16051,"line":{"s":330,"e":330,"code":["| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":22,"e":30}},"dim":["","paragraph.66","text.60"],"code":"      | "},{"id":"/root/children/66/children/61","type":"inlineCode","loc":{"start":16051,"end":16072,"line":{"s":330,"e":330,"code":["| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":30,"e":51}},"dim":["","paragraph.66","inlineCode.61"],"code":"`window.closeGlass()`"},{"id":"/root/children/66/children/62","type":"text","loc":{"start":16072,"end":16076,"line":{"s":330,"e":330,"code":["| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":51,"e":55}},"dim":["","paragraph.66","text.62"],"code":" -> "},{"id":"/root/children/66/children/63","type":"inlineCode","loc":{"start":16076,"end":16093,"line":{"s":330,"e":330,"code":["| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":55,"e":72}},"dim":["","paragraph.66","inlineCode.63"],"code":"`goToPrev(STATE)`"},{"id":"/root/children/66/children/64","type":"text","loc":{"start":16093,"end":16129,"line":{"s":330,"e":330,"code":["| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |"]},"column":{"s":72,"e":108}},"dim":["","paragraph.66","text.64"],"code":"                                   |"},{"id":"/root/children/67","type":"heading","loc":{"start":16131,"end":16152,"line":{"s":332,"e":332,"code":["### Data Flow: Search"]},"column":{"s":0,"e":21}},"dim":["","heading.67"],"code":"### Data Flow: Search","symbName":"heading","symbRange":[16154,16701],"symbRangeL":[332,346],"outerCode":"\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```","outerHtml":"\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>"},{"id":"/root/children/67/children/0","type":"text","loc":{"start":16135,"end":16152,"line":{"s":332,"e":332,"code":["### Data Flow: Search"]},"column":{"s":4,"e":21}},"dim":["","heading.67","text.0"],"code":"Data Flow: Search"},{"id":"/root/children/68","type":"code","loc":{"start":16154,"end":16701,"line":{"s":334,"e":345,"code":["```","user types in #menu-input","  -> Autocomplete.onSearch({ currentValue })","  -> glassSearchRun(queryString, ...)","       analyzeQuery() parses sources + terms (history|files|fragments)","       |-- searchInHistory()    -> filters STATE.history in-memory","       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text","       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)","       returns reducedResult[]","  -> Autocomplete.onResults(matches) renders <li> items","  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)","```"]},"column":{"s":0,"e":3}},"dim":["","code.68"],"code":"```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```","symbName":"code","symbRange":[16703,16737],"symbRangeL":[null,348],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>"},{"id":"/root/children/69","type":"heading","loc":{"start":16703,"end":16737,"line":{"s":347,"e":347,"code":["### Data Flow: MQ (real-time sync)"]},"column":{"s":0,"e":34}},"dim":["","heading.69"],"code":"### Data Flow: MQ (real-time sync)","symbName":"heading","symbRange":[16739,17401],"symbRangeL":[347,363],"outerCode":"\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```","outerHtml":"\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>"},{"id":"/root/children/69/children/0","type":"text","loc":{"start":16707,"end":16737,"line":{"s":347,"e":347,"code":["### Data Flow: MQ (real-time sync)"]},"column":{"s":4,"e":34}},"dim":["","heading.69","text.0"],"code":"Data Flow: MQ (real-time sync)"},{"id":"/root/children/70","type":"code","loc":{"start":16739,"end":17401,"line":{"s":349,"e":362,"code":["```","stompClient.subscribe(exFanout, mqFanoutConsumer)","  incoming message:","    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)","    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper","    reload: 'map'    -> window.location.reload()","    reload: { space } -> resetMachine() + refreshTiles()","    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})","","outgoing (if propagation-*-out checkboxes are on):","    space position changes -> emitFanoutMessage(payload)","    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })","    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})","```"]},"column":{"s":0,"e":3}},"dim":["","code.70"],"code":"```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```","symbName":"code","symbRange":[17403,22013],"symbRangeL":[null,404],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>"},{"id":"/root/children/71","type":"heading","loc":{"start":17403,"end":17424,"line":{"s":364,"e":364,"code":["### Persistence Model"]},"column":{"s":0,"e":21}},"dim":["","heading.71"],"code":"### Persistence Model","symbName":"heading","symbRange":[17426,18492],"symbRangeL":[364,380],"outerCode":"\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |","outerHtml":"\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>"},{"id":"/root/children/71/children/0","type":"text","loc":{"start":17407,"end":17424,"line":{"s":364,"e":364,"code":["### Persistence Model"]},"column":{"s":4,"e":21}},"dim":["","heading.71","text.0"],"code":"Persistence Model"},{"id":"/root/children/72","type":"paragraph","loc":{"start":17426,"end":17507,"line":{"s":366,"e":366,"code":["All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:"]},"column":{"s":0,"e":81}},"dim":["","paragraph.72"],"code":"All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:"},{"id":"/root/children/72/children/0","type":"text","loc":{"start":17426,"end":17449,"line":{"s":366,"e":366,"code":["All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:"]},"column":{"s":0,"e":23}},"dim":["","paragraph.72","text.0"],"code":"All state is scoped by "},{"id":"/root/children/72/children/1","type":"inlineCode","loc":{"start":17449,"end":17470,"line":{"s":366,"e":366,"code":["All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:"]},"column":{"s":23,"e":44}},"dim":["","paragraph.72","inlineCode.1"],"code":"`windowStoreKey(key)`"},{"id":"/root/children/72/children/2","type":"text","loc":{"start":17470,"end":17491,"line":{"s":366,"e":366,"code":["All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:"]},"column":{"s":44,"e":65}},"dim":["","paragraph.72","text.2"],"code":" which prefixes with "},{"id":"/root/children/72/children/3","type":"inlineCode","loc":{"start":17491,"end":17506,"line":{"s":366,"e":366,"code":["All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:"]},"column":{"s":65,"e":80}},"dim":["","paragraph.72","inlineCode.3"],"code":"`{idBui, repo}`"},{"id":"/root/children/72/children/4","type":"text","loc":{"start":17506,"end":17507,"line":{"s":366,"e":366,"code":["All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:"]},"column":{"s":80,"e":81}},"dim":["","paragraph.72","text.4"],"code":":"},{"id":"/root/children/73","type":"paragraph","loc":{"start":17509,"end":18492,"line":{"s":368,"e":379,"code":["| Key pattern                          | What it stores                         |","| ------------------------------------ | -------------------------------------- |","| `@lastRoute`                         | last visited hash                      |","| `@lastSearchInputValue`              | last search query                      |","| `@lastGlassState`                    | was glass open? (bool)                 |","| `history`                            | array of visited hashes                |","| `editorPos:{uri}`                    | cursor/selection for each file         |","| `@theSTATE.ast.space.mapName`        | last viewed map name                   |","| `@theSTATE.ast.space.position:{map}` | last map viewport                      |","| `options/glass/*`                    | all checkbox states from options panel |","| `.theme`                             | light/dark (no repo prefix)            |","| `.secret/glass/portal-token`         | auth token (no repo prefix)            |"]},"column":{"s":0,"e":81}},"dim":["","paragraph.73"],"code":"| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |"},{"id":"/root/children/73/children/0","type":"text","loc":{"start":17509,"end":17675,"line":{"s":368,"e":370,"code":["| Key pattern                          | What it stores                         |","| ------------------------------------ | -------------------------------------- |","| `@lastRoute`                         | last visited hash                      |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.73","text.0"],"code":"| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| "},{"id":"/root/children/73/children/1","type":"inlineCode","loc":{"start":17675,"end":17687,"line":{"s":370,"e":370,"code":["| `@lastRoute`                         | last visited hash                      |"]},"column":{"s":2,"e":14}},"dim":["","paragraph.73","inlineCode.1"],"code":"`@lastRoute`"},{"id":"/root/children/73/children/2","type":"text","loc":{"start":17687,"end":17757,"line":{"s":370,"e":371,"code":["| `@lastRoute`                         | last visited hash                      |","| `@lastSearchInputValue`              | last search query                      |"]},"column":{"s":14,"e":2}},"dim":["","paragraph.73","text.2"],"code":"                         | last visited hash                      |\n| "},{"id":"/root/children/73/children/3","type":"inlineCode","loc":{"start":17757,"end":17780,"line":{"s":371,"e":371,"code":["| `@lastSearchInputValue`              | last search query                      |"]},"column":{"s":2,"e":25}},"dim":["","paragraph.73","inlineCode.3"],"code":"`@lastSearchInputValue`"},{"id":"/root/children/73/children/4","type":"text","loc":{"start":17780,"end":17839,"line":{"s":371,"e":372,"code":["| `@lastSearchInputValue`              | last search query                      |","| `@lastGlassState`                    | was glass open? (bool)                 |"]},"column":{"s":25,"e":2}},"dim":["","paragraph.73","text.4"],"code":"              | last search query                      |\n| "},{"id":"/root/children/73/children/5","type":"inlineCode","loc":{"start":17839,"end":17856,"line":{"s":372,"e":372,"code":["| `@lastGlassState`                    | was glass open? (bool)                 |"]},"column":{"s":2,"e":19}},"dim":["","paragraph.73","inlineCode.5"],"code":"`@lastGlassState`"},{"id":"/root/children/73/children/6","type":"text","loc":{"start":17856,"end":17921,"line":{"s":372,"e":373,"code":["| `@lastGlassState`                    | was glass open? (bool)                 |","| `history`                            | array of visited hashes                |"]},"column":{"s":19,"e":2}},"dim":["","paragraph.73","text.6"],"code":"                    | was glass open? (bool)                 |\n| "},{"id":"/root/children/73/children/7","type":"inlineCode","loc":{"start":17921,"end":17930,"line":{"s":373,"e":373,"code":["| `history`                            | array of visited hashes                |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.73","inlineCode.7"],"code":"`history`"},{"id":"/root/children/73/children/8","type":"text","loc":{"start":17930,"end":18003,"line":{"s":373,"e":374,"code":["| `history`                            | array of visited hashes                |","| `editorPos:{uri}`                    | cursor/selection for each file         |"]},"column":{"s":11,"e":2}},"dim":["","paragraph.73","text.8"],"code":"                            | array of visited hashes                |\n| "},{"id":"/root/children/73/children/9","type":"inlineCode","loc":{"start":18003,"end":18020,"line":{"s":374,"e":374,"code":["| `editorPos:{uri}`                    | cursor/selection for each file         |"]},"column":{"s":2,"e":19}},"dim":["","paragraph.73","inlineCode.9"],"code":"`editorPos:{uri}`"},{"id":"/root/children/73/children/10","type":"text","loc":{"start":18020,"end":18085,"line":{"s":374,"e":375,"code":["| `editorPos:{uri}`                    | cursor/selection for each file         |","| `@theSTATE.ast.space.mapName`        | last viewed map name                   |"]},"column":{"s":19,"e":2}},"dim":["","paragraph.73","text.10"],"code":"                    | cursor/selection for each file         |\n| "},{"id":"/root/children/73/children/11","type":"inlineCode","loc":{"start":18085,"end":18114,"line":{"s":375,"e":375,"code":["| `@theSTATE.ast.space.mapName`        | last viewed map name                   |"]},"column":{"s":2,"e":31}},"dim":["","paragraph.73","inlineCode.11"],"code":"`@theSTATE.ast.space.mapName`"},{"id":"/root/children/73/children/12","type":"text","loc":{"start":18114,"end":18167,"line":{"s":375,"e":376,"code":["| `@theSTATE.ast.space.mapName`        | last viewed map name                   |","| `@theSTATE.ast.space.position:{map}` | last map viewport                      |"]},"column":{"s":31,"e":2}},"dim":["","paragraph.73","text.12"],"code":"        | last viewed map name                   |\n| "},{"id":"/root/children/73/children/13","type":"inlineCode","loc":{"start":18167,"end":18203,"line":{"s":376,"e":376,"code":["| `@theSTATE.ast.space.position:{map}` | last map viewport                      |"]},"column":{"s":2,"e":38}},"dim":["","paragraph.73","inlineCode.13"],"code":"`@theSTATE.ast.space.position:{map}`"},{"id":"/root/children/73/children/14","type":"text","loc":{"start":18203,"end":18249,"line":{"s":376,"e":377,"code":["| `@theSTATE.ast.space.position:{map}` | last map viewport                      |","| `options/glass/*`                    | all checkbox states from options panel |"]},"column":{"s":38,"e":2}},"dim":["","paragraph.73","text.14"],"code":" | last map viewport                      |\n| "},{"id":"/root/children/73/children/15","type":"inlineCode","loc":{"start":18249,"end":18266,"line":{"s":377,"e":377,"code":["| `options/glass/*`                    | all checkbox states from options panel |"]},"column":{"s":2,"e":19}},"dim":["","paragraph.73","inlineCode.15"],"code":"`options/glass/*`"},{"id":"/root/children/73/children/16","type":"text","loc":{"start":18266,"end":18331,"line":{"s":377,"e":378,"code":["| `options/glass/*`                    | all checkbox states from options panel |","| `.theme`                             | light/dark (no repo prefix)            |"]},"column":{"s":19,"e":2}},"dim":["","paragraph.73","text.16"],"code":"                    | all checkbox states from options panel |\n| "},{"id":"/root/children/73/children/17","type":"inlineCode","loc":{"start":18331,"end":18339,"line":{"s":378,"e":378,"code":["| `.theme`                             | light/dark (no repo prefix)            |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.73","inlineCode.17"],"code":"`.theme`"},{"id":"/root/children/73/children/18","type":"text","loc":{"start":18339,"end":18413,"line":{"s":378,"e":379,"code":["| `.theme`                             | light/dark (no repo prefix)            |","| `.secret/glass/portal-token`         | auth token (no repo prefix)            |"]},"column":{"s":10,"e":2}},"dim":["","paragraph.73","text.18"],"code":"                             | light/dark (no repo prefix)            |\n| "},{"id":"/root/children/73/children/19","type":"inlineCode","loc":{"start":18413,"end":18441,"line":{"s":379,"e":379,"code":["| `.secret/glass/portal-token`         | auth token (no repo prefix)            |"]},"column":{"s":2,"e":30}},"dim":["","paragraph.73","inlineCode.19"],"code":"`.secret/glass/portal-token`"},{"id":"/root/children/73/children/20","type":"text","loc":{"start":18441,"end":18492,"line":{"s":379,"e":379,"code":["| `.secret/glass/portal-token`         | auth token (no repo prefix)            |"]},"column":{"s":30,"e":81}},"dim":["","paragraph.73","text.20"],"code":"         | auth token (no repo prefix)            |"},{"id":"/root/children/74","type":"heading","loc":{"start":18494,"end":18526,"line":{"s":381,"e":381,"code":["## ssss.js - The Utility Library"]},"column":{"s":0,"e":32}},"dim":["","heading.74"],"code":"## ssss.js - The Utility Library","symbName":"heading","symbRange":[18528,18758],"symbRangeL":[381,384],"outerCode":"\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.","outerHtml":"\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>"},{"id":"/root/children/74/children/0","type":"text","loc":{"start":18497,"end":18526,"line":{"s":381,"e":381,"code":["## ssss.js - The Utility Library"]},"column":{"s":3,"e":32}},"dim":["","heading.74","text.0"],"code":"ssss.js - The Utility Library"},{"id":"/root/children/75","type":"paragraph","loc":{"start":18528,"end":18758,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":0,"e":230}},"dim":["","paragraph.75"],"code":"`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."},{"id":"/root/children/75/children/0","type":"inlineCode","loc":{"start":18528,"end":18537,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":0,"e":9}},"dim":["","paragraph.75","inlineCode.0"],"code":"`ssss.js`"},{"id":"/root/children/75/children/1","type":"text","loc":{"start":18537,"end":18539,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":9,"e":11}},"dim":["","paragraph.75","text.1"],"code":" ("},{"id":"/root/children/75/children/2","type":"inlineCode","loc":{"start":18539,"end":18566,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":11,"e":38}},"dim":["","paragraph.75","inlineCode.2"],"code":"`/source/assets/s4/ssss.js`"},{"id":"/root/children/75/children/3","type":"text","loc":{"start":18566,"end":18670,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":38,"e":142}},"dim":["","paragraph.75","text.3"],"code":", ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as "},{"id":"/root/children/75/children/4","type":"inlineCode","loc":{"start":18670,"end":18704,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":142,"e":176}},"dim":["","paragraph.75","inlineCode.4"],"code":"`import ssss from '...s4/ssss.js'`"},{"id":"/root/children/75/children/5","type":"text","loc":{"start":18704,"end":18731,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":176,"e":203}},"dim":["","paragraph.75","text.5"],"code":" and passed around via the "},{"id":"/root/children/75/children/6","type":"inlineCode","loc":{"start":18731,"end":18752,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":203,"e":224}},"dim":["","paragraph.75","inlineCode.6"],"code":"`player({ssss, ...})`"},{"id":"/root/children/75/children/7","type":"text","loc":{"start":18752,"end":18758,"line":{"s":383,"e":383,"code":["`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call."]},"column":{"s":224,"e":230}},"dim":["","paragraph.75","text.7"],"code":" call."},{"id":"/root/children/76","type":"heading","loc":{"start":18760,"end":18795,"line":{"s":385,"e":385,"code":["### What ssss provides to player.js"]},"column":{"s":0,"e":35}},"dim":["","heading.76"],"code":"### What ssss provides to player.js","symbName":"heading","symbRange":[18797,21991],"symbRangeL":[385,402],"outerCode":"\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |","outerHtml":"\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>"},{"id":"/root/children/76/children/0","type":"text","loc":{"start":18764,"end":18795,"line":{"s":385,"e":385,"code":["### What ssss provides to player.js"]},"column":{"s":4,"e":35}},"dim":["","heading.76","text.0"],"code":"What ssss provides to player.js"},{"id":"/root/children/77","type":"paragraph","loc":{"start":18797,"end":21991,"line":{"s":387,"e":401,"code":["| Category            | Functions / Objects                                                                                | Used for                                                                              |","| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |","| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |","| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |","| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |","| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |","| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |","| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |","| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |","| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |","| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |","| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |","| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |","| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |","| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":0,"e":212}},"dim":["","paragraph.77"],"code":"| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"},{"id":"/root/children/77/children/0","type":"text","loc":{"start":18797,"end":19225,"line":{"s":387,"e":389,"code":["| Category            | Functions / Objects                                                                                | Used for                                                                              |","| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |","| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.77","text.0"],"code":"| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/77/children/1","type":"strong","loc":{"start":19225,"end":19233,"line":{"s":389,"e":389,"code":["| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.77","strong.1"],"code":"**Boot**"},{"id":"/root/children/77/children/1/children/0","type":"text","loc":{"start":19227,"end":19231,"line":{"s":389,"e":389,"code":["| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |"]},"column":{"s":4,"e":8}},"dim":["","paragraph.77","strong.1","text.0"],"code":"Boot"},{"id":"/root/children/77/children/2","type":"text","loc":{"start":19233,"end":19247,"line":{"s":389,"e":389,"code":["| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |"]},"column":{"s":10,"e":24}},"dim":["","paragraph.77","text.2"],"code":"            | "},{"id":"/root/children/77/children/3","type":"inlineCode","loc":{"start":19247,"end":19257,"line":{"s":389,"e":389,"code":["| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |"]},"column":{"s":24,"e":34}},"dim":["","paragraph.77","inlineCode.3"],"code":"`bootS4()`"},{"id":"/root/children/77/children/4","type":"text","loc":{"start":19257,"end":19387,"line":{"s":389,"e":389,"code":["| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |"]},"column":{"s":34,"e":164}},"dim":["","paragraph.77","text.4"],"code":"                                                                                         | Hash-router setup, route registration, "},{"id":"/root/children/77/children/5","type":"inlineCode","loc":{"start":19387,"end":19402,"line":{"s":389,"e":389,"code":["| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |"]},"column":{"s":164,"e":179}},"dim":["","paragraph.77","inlineCode.5"],"code":"`routeFallback`"},{"id":"/root/children/77/children/6","type":"text","loc":{"start":19402,"end":19438,"line":{"s":389,"e":390,"code":["| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |","| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":179,"e":2}},"dim":["","paragraph.77","text.6"],"code":", initial navigate              |\n| "},{"id":"/root/children/77/children/7","type":"strong","loc":{"start":19438,"end":19447,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.77","strong.7"],"code":"**Fetch**"},{"id":"/root/children/77/children/7/children/0","type":"text","loc":{"start":19440,"end":19445,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.77","strong.7","text.0"],"code":"Fetch"},{"id":"/root/children/77/children/8","type":"text","loc":{"start":19447,"end":19460,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":11,"e":24}},"dim":["","paragraph.77","text.8"],"code":"           | "},{"id":"/root/children/77/children/9","type":"inlineCode","loc":{"start":19460,"end":19473,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":24,"e":37}},"dim":["","paragraph.77","inlineCode.9"],"code":"`fetchJson()`"},{"id":"/root/children/77/children/10","type":"text","loc":{"start":19473,"end":19475,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":37,"e":39}},"dim":["","paragraph.77","text.10"],"code":", "},{"id":"/root/children/77/children/11","type":"inlineCode","loc":{"start":19475,"end":19492,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":39,"e":56}},"dim":["","paragraph.77","inlineCode.11"],"code":"`fetchWithETag()`"},{"id":"/root/children/77/children/12","type":"text","loc":{"start":19492,"end":19494,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":56,"e":58}},"dim":["","paragraph.77","text.12"],"code":", "},{"id":"/root/children/77/children/13","type":"inlineCode","loc":{"start":19494,"end":19514,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":58,"e":78}},"dim":["","paragraph.77","inlineCode.13"],"code":"`fetchWithTimeout()`"},{"id":"/root/children/77/children/14","type":"text","loc":{"start":19514,"end":19516,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":78,"e":80}},"dim":["","paragraph.77","text.14"],"code":", "},{"id":"/root/children/77/children/15","type":"inlineCode","loc":{"start":19516,"end":19529,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":80,"e":93}},"dim":["","paragraph.77","inlineCode.15"],"code":"`fetchHead()`"},{"id":"/root/children/77/children/16","type":"text","loc":{"start":19529,"end":19531,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":93,"e":95}},"dim":["","paragraph.77","text.16"],"code":", "},{"id":"/root/children/77/children/17","type":"inlineCode","loc":{"start":19531,"end":19543,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":95,"e":107}},"dim":["","paragraph.77","inlineCode.17"],"code":"`fetchAny()`"},{"id":"/root/children/77/children/18","type":"text","loc":{"start":19543,"end":19545,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":107,"e":109}},"dim":["","paragraph.77","text.18"],"code":", "},{"id":"/root/children/77/children/19","type":"inlineCode","loc":{"start":19545,"end":19558,"line":{"s":390,"e":390,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |"]},"column":{"s":109,"e":122}},"dim":["","paragraph.77","inlineCode.19"],"code":"`fetchHtml()`"},{"id":"/root/children/77/children/20","type":"text","loc":{"start":19558,"end":19651,"line":{"s":390,"e":391,"code":["| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |","| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":122,"e":2}},"dim":["","paragraph.77","text.20"],"code":" | All HTTP with ETag caching in localStorage                                            |\n| "},{"id":"/root/children/77/children/21","type":"strong","loc":{"start":19651,"end":19668,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":2,"e":19}},"dim":["","paragraph.77","strong.21"],"code":"**Protocol URLs**"},{"id":"/root/children/77/children/21/children/0","type":"text","loc":{"start":19653,"end":19666,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":4,"e":17}},"dim":["","paragraph.77","strong.21","text.0"],"code":"Protocol URLs"},{"id":"/root/children/77/children/22","type":"text","loc":{"start":19668,"end":19673,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":19,"e":24}},"dim":["","paragraph.77","text.22"],"code":"   | "},{"id":"/root/children/77/children/23","type":"inlineCode","loc":{"start":19673,"end":19683,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":24,"e":34}},"dim":["","paragraph.77","inlineCode.23"],"code":"`Url(url)`"},{"id":"/root/children/77/children/24","type":"text","loc":{"start":19683,"end":19783,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":34,"e":134}},"dim":["","paragraph.77","text.24"],"code":"                                                                                         | Resolves "},{"id":"/root/children/77/children/25","type":"inlineCode","loc":{"start":19783,"end":19794,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":134,"e":145}},"dim":["","paragraph.77","inlineCode.25"],"code":"`assets://`"},{"id":"/root/children/77/children/26","type":"text","loc":{"start":19794,"end":19796,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":145,"e":147}},"dim":["","paragraph.77","text.26"],"code":", "},{"id":"/root/children/77/children/27","type":"inlineCode","loc":{"start":19796,"end":19805,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":147,"e":156}},"dim":["","paragraph.77","inlineCode.27"],"code":"`repo://`"},{"id":"/root/children/77/children/28","type":"text","loc":{"start":19805,"end":19807,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":156,"e":158}},"dim":["","paragraph.77","text.28"],"code":", "},{"id":"/root/children/77/children/29","type":"inlineCode","loc":{"start":19807,"end":19816,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":158,"e":167}},"dim":["","paragraph.77","inlineCode.29"],"code":"`crud://`"},{"id":"/root/children/77/children/30","type":"text","loc":{"start":19816,"end":19818,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":167,"e":169}},"dim":["","paragraph.77","text.30"],"code":", "},{"id":"/root/children/77/children/31","type":"inlineCode","loc":{"start":19818,"end":19825,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":169,"e":176}},"dim":["","paragraph.77","inlineCode.31"],"code":"`mq://`"},{"id":"/root/children/77/children/32","type":"text","loc":{"start":19825,"end":19835,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":176,"e":186}},"dim":["","paragraph.77","text.32"],"code":" etc. via "},{"id":"/root/children/77/children/33","type":"inlineCode","loc":{"start":19835,"end":19851,"line":{"s":391,"e":391,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |"]},"column":{"s":186,"e":202}},"dim":["","paragraph.77","inlineCode.33"],"code":"`protocolRouter`"},{"id":"/root/children/77/children/34","type":"text","loc":{"start":19851,"end":19864,"line":{"s":391,"e":392,"code":["| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |","| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |"]},"column":{"s":202,"e":2}},"dim":["","paragraph.77","text.34"],"code":" map     |\n| "},{"id":"/root/children/77/children/35","type":"strong","loc":{"start":19864,"end":19870,"line":{"s":392,"e":392,"code":["| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.77","strong.35"],"code":"**MQ**"},{"id":"/root/children/77/children/35/children/0","type":"text","loc":{"start":19866,"end":19868,"line":{"s":392,"e":392,"code":["| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |"]},"column":{"s":4,"e":6}},"dim":["","paragraph.77","strong.35","text.0"],"code":"MQ"},{"id":"/root/children/77/children/36","type":"text","loc":{"start":19870,"end":19886,"line":{"s":392,"e":392,"code":["| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |"]},"column":{"s":8,"e":24}},"dim":["","paragraph.77","text.36"],"code":"              | "},{"id":"/root/children/77/children/37","type":"inlineCode","loc":{"start":19886,"end":19901,"line":{"s":392,"e":392,"code":["| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |"]},"column":{"s":24,"e":39}},"dim":["","paragraph.77","inlineCode.37"],"code":"`initMqStomp()`"},{"id":"/root/children/77/children/38","type":"text","loc":{"start":19901,"end":20024,"line":{"s":392,"e":392,"code":["| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |"]},"column":{"s":39,"e":162}},"dim":["","paragraph.77","text.38"],"code":"                                                                                    | STOMP/WebSocket client init, returns "},{"id":"/root/children/77/children/39","type":"inlineCode","loc":{"start":20024,"end":20067,"line":{"s":392,"e":392,"code":["| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |"]},"column":{"s":162,"e":205}},"dim":["","paragraph.77","inlineCode.39"],"code":"`{stompClient, exFanout, exQueue, exTopic}`"},{"id":"/root/children/77/children/40","type":"text","loc":{"start":20067,"end":20077,"line":{"s":392,"e":393,"code":["| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |","| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":205,"e":2}},"dim":["","paragraph.77","text.40"],"code":"      |\n| "},{"id":"/root/children/77/children/41","type":"strong","loc":{"start":20077,"end":20090,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":2,"e":15}},"dim":["","paragraph.77","strong.41"],"code":"**Rendering**"},{"id":"/root/children/77/children/41/children/0","type":"text","loc":{"start":20079,"end":20088,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":4,"e":13}},"dim":["","paragraph.77","strong.41","text.0"],"code":"Rendering"},{"id":"/root/children/77/children/42","type":"text","loc":{"start":20090,"end":20099,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":15,"e":24}},"dim":["","paragraph.77","text.42"],"code":"       | "},{"id":"/root/children/77/children/43","type":"inlineCode","loc":{"start":20099,"end":20112,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":24,"e":37}},"dim":["","paragraph.77","inlineCode.43"],"code":"`mddRender()`"},{"id":"/root/children/77/children/44","type":"text","loc":{"start":20112,"end":20114,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":37,"e":39}},"dim":["","paragraph.77","text.44"],"code":", "},{"id":"/root/children/77/children/45","type":"inlineCode","loc":{"start":20114,"end":20128,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":39,"e":53}},"dim":["","paragraph.77","inlineCode.45"],"code":"`jsonRender()`"},{"id":"/root/children/77/children/46","type":"text","loc":{"start":20128,"end":20130,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":53,"e":55}},"dim":["","paragraph.77","text.46"],"code":", "},{"id":"/root/children/77/children/47","type":"inlineCode","loc":{"start":20130,"end":20144,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":55,"e":69}},"dim":["","paragraph.77","inlineCode.47"],"code":"`htmlRender()`"},{"id":"/root/children/77/children/48","type":"text","loc":{"start":20144,"end":20146,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":69,"e":71}},"dim":["","paragraph.77","text.48"],"code":", "},{"id":"/root/children/77/children/49","type":"inlineCode","loc":{"start":20146,"end":20162,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":71,"e":87}},"dim":["","paragraph.77","inlineCode.49"],"code":"`renderCoreUi()`"},{"id":"/root/children/77/children/50","type":"text","loc":{"start":20162,"end":20164,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":87,"e":89}},"dim":["","paragraph.77","text.50"],"code":", "},{"id":"/root/children/77/children/51","type":"inlineCode","loc":{"start":20164,"end":20182,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":89,"e":107}},"dim":["","paragraph.77","inlineCode.51"],"code":"`renderMarkdown()`"},{"id":"/root/children/77/children/52","type":"text","loc":{"start":20182,"end":20207,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":107,"e":132}},"dim":["","paragraph.77","text.52"],"code":"                | Render "},{"id":"/root/children/77/children/53","type":"inlineCode","loc":{"start":20207,"end":20214,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":132,"e":139}},"dim":["","paragraph.77","inlineCode.53"],"code":"`<mdd>`"},{"id":"/root/children/77/children/54","type":"text","loc":{"start":20214,"end":20216,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":139,"e":141}},"dim":["","paragraph.77","text.54"],"code":", "},{"id":"/root/children/77/children/55","type":"inlineCode","loc":{"start":20216,"end":20224,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":141,"e":149}},"dim":["","paragraph.77","inlineCode.55"],"code":"`<json>`"},{"id":"/root/children/77/children/56","type":"text","loc":{"start":20224,"end":20226,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":149,"e":151}},"dim":["","paragraph.77","text.56"],"code":", "},{"id":"/root/children/77/children/57","type":"inlineCode","loc":{"start":20226,"end":20233,"line":{"s":393,"e":393,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |"]},"column":{"s":151,"e":158}},"dim":["","paragraph.77","inlineCode.57"],"code":"`<htm>`"},{"id":"/root/children/77/children/58","type":"text","loc":{"start":20233,"end":20290,"line":{"s":393,"e":394,"code":["| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |","| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":158,"e":2}},"dim":["","paragraph.77","text.58"],"code":" tags into HTML                                      |\n| "},{"id":"/root/children/77/children/59","type":"strong","loc":{"start":20290,"end":20302,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":2,"e":14}},"dim":["","paragraph.77","strong.59"],"code":"**Webhooks**"},{"id":"/root/children/77/children/59/children/0","type":"text","loc":{"start":20292,"end":20300,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":4,"e":12}},"dim":["","paragraph.77","strong.59","text.0"],"code":"Webhooks"},{"id":"/root/children/77/children/60","type":"text","loc":{"start":20302,"end":20312,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":14,"e":24}},"dim":["","paragraph.77","text.60"],"code":"        | "},{"id":"/root/children/77/children/61","type":"inlineCode","loc":{"start":20312,"end":20335,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":24,"e":47}},"dim":["","paragraph.77","inlineCode.61"],"code":"`createWebHookAction()`"},{"id":"/root/children/77/children/62","type":"text","loc":{"start":20335,"end":20337,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":47,"e":49}},"dim":["","paragraph.77","text.62"],"code":", "},{"id":"/root/children/77/children/63","type":"inlineCode","loc":{"start":20337,"end":20362,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":49,"e":74}},"dim":["","paragraph.77","inlineCode.63"],"code":"`emitActionWithWebhook()`"},{"id":"/root/children/77/children/64","type":"text","loc":{"start":20362,"end":20364,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":74,"e":76}},"dim":["","paragraph.77","text.64"],"code":", "},{"id":"/root/children/77/children/65","type":"inlineCode","loc":{"start":20364,"end":20389,"line":{"s":394,"e":394,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |"]},"column":{"s":76,"e":101}},"dim":["","paragraph.77","inlineCode.65"],"code":"`createMqMessageAction()`"},{"id":"/root/children/77/children/66","type":"text","loc":{"start":20389,"end":20503,"line":{"s":394,"e":395,"code":["| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |","| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":101,"e":2}},"dim":["","paragraph.77","text.66"],"code":"                      | Webhook POST with streaming response, MQ publish helpers                              |\n| "},{"id":"/root/children/77/children/67","type":"strong","loc":{"start":20503,"end":20510,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.77","strong.67"],"code":"**DOM**"},{"id":"/root/children/77/children/67/children/0","type":"text","loc":{"start":20505,"end":20508,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":4,"e":7}},"dim":["","paragraph.77","strong.67","text.0"],"code":"DOM"},{"id":"/root/children/77/children/68","type":"text","loc":{"start":20510,"end":20525,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":9,"e":24}},"dim":["","paragraph.77","text.68"],"code":"             | "},{"id":"/root/children/77/children/69","type":"inlineCode","loc":{"start":20525,"end":20541,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":24,"e":40}},"dim":["","paragraph.77","inlineCode.69"],"code":"`getNodeTrail()`"},{"id":"/root/children/77/children/70","type":"text","loc":{"start":20541,"end":20543,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":40,"e":42}},"dim":["","paragraph.77","text.70"],"code":", "},{"id":"/root/children/77/children/71","type":"inlineCode","loc":{"start":20543,"end":20561,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":42,"e":60}},"dim":["","paragraph.77","inlineCode.71"],"code":"`getDeepParents()`"},{"id":"/root/children/77/children/72","type":"text","loc":{"start":20561,"end":20563,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":60,"e":62}},"dim":["","paragraph.77","text.72"],"code":", "},{"id":"/root/children/77/children/73","type":"inlineCode","loc":{"start":20563,"end":20590,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":62,"e":89}},"dim":["","paragraph.77","inlineCode.73"],"code":"`addTrackedEventListener()`"},{"id":"/root/children/77/children/74","type":"text","loc":{"start":20590,"end":20592,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":89,"e":91}},"dim":["","paragraph.77","text.74"],"code":", "},{"id":"/root/children/77/children/75","type":"inlineCode","loc":{"start":20592,"end":20616,"line":{"s":395,"e":395,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |"]},"column":{"s":91,"e":115}},"dim":["","paragraph.77","inlineCode.75"],"code":"`removeEventListeners()`"},{"id":"/root/children/77/children/76","type":"text","loc":{"start":20616,"end":20716,"line":{"s":395,"e":396,"code":["| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |","| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":115,"e":2}},"dim":["","paragraph.77","text.76"],"code":"        | DOM traversal, tracked event binding/cleanup                                          |\n| "},{"id":"/root/children/77/children/77","type":"strong","loc":{"start":20716,"end":20730,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.77","strong.77"],"code":"**Navigation**"},{"id":"/root/children/77/children/77/children/0","type":"text","loc":{"start":20718,"end":20728,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":4,"e":14}},"dim":["","paragraph.77","strong.77","text.0"],"code":"Navigation"},{"id":"/root/children/77/children/78","type":"text","loc":{"start":20730,"end":20738,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":16,"e":24}},"dim":["","paragraph.77","text.78"],"code":"      | "},{"id":"/root/children/77/children/79","type":"inlineCode","loc":{"start":20738,"end":20750,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":24,"e":36}},"dim":["","paragraph.77","inlineCode.79"],"code":"`sakaInit()`"},{"id":"/root/children/77/children/80","type":"text","loc":{"start":20750,"end":20752,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":36,"e":38}},"dim":["","paragraph.77","text.80"],"code":", "},{"id":"/root/children/77/children/81","type":"inlineCode","loc":{"start":20752,"end":20770,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":38,"e":56}},"dim":["","paragraph.77","inlineCode.81"],"code":"`insertTooltips()`"},{"id":"/root/children/77/children/82","type":"text","loc":{"start":20770,"end":20772,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":56,"e":58}},"dim":["","paragraph.77","text.82"],"code":", "},{"id":"/root/children/77/children/83","type":"inlineCode","loc":{"start":20772,"end":20789,"line":{"s":396,"e":396,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |"]},"column":{"s":58,"e":75}},"dim":["","paragraph.77","inlineCode.83"],"code":"`clearTooltips()`"},{"id":"/root/children/77/children/84","type":"text","loc":{"start":20789,"end":20929,"line":{"s":396,"e":397,"code":["| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |","| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":75,"e":2}},"dim":["","paragraph.77","text.84"],"code":"                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| "},{"id":"/root/children/77/children/85","type":"strong","loc":{"start":20929,"end":20937,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.77","strong.85"],"code":"**Misc**"},{"id":"/root/children/77/children/85/children/0","type":"text","loc":{"start":20931,"end":20935,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":4,"e":8}},"dim":["","paragraph.77","strong.85","text.0"],"code":"Misc"},{"id":"/root/children/77/children/86","type":"text","loc":{"start":20937,"end":20951,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":10,"e":24}},"dim":["","paragraph.77","text.86"],"code":"            | "},{"id":"/root/children/77/children/87","type":"inlineCode","loc":{"start":20951,"end":20963,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":24,"e":36}},"dim":["","paragraph.77","inlineCode.87"],"code":"`debounce()`"},{"id":"/root/children/77/children/88","type":"text","loc":{"start":20963,"end":20965,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":36,"e":38}},"dim":["","paragraph.77","text.88"],"code":", "},{"id":"/root/children/77/children/89","type":"inlineCode","loc":{"start":20965,"end":20974,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":38,"e":47}},"dim":["","paragraph.77","inlineCode.89"],"code":"`sleep()`"},{"id":"/root/children/77/children/90","type":"text","loc":{"start":20974,"end":20976,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":47,"e":49}},"dim":["","paragraph.77","text.90"],"code":", "},{"id":"/root/children/77/children/91","type":"inlineCode","loc":{"start":20976,"end":20995,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":49,"e":68}},"dim":["","paragraph.77","inlineCode.91"],"code":"`isMobileAppMode()`"},{"id":"/root/children/77/children/92","type":"text","loc":{"start":20995,"end":20997,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":68,"e":70}},"dim":["","paragraph.77","text.92"],"code":", "},{"id":"/root/children/77/children/93","type":"inlineCode","loc":{"start":20997,"end":21012,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":70,"e":85}},"dim":["","paragraph.77","inlineCode.93"],"code":"`scaleValues()`"},{"id":"/root/children/77/children/94","type":"text","loc":{"start":21012,"end":21014,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":85,"e":87}},"dim":["","paragraph.77","text.94"],"code":", "},{"id":"/root/children/77/children/95","type":"inlineCode","loc":{"start":21014,"end":21032,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":87,"e":105}},"dim":["","paragraph.77","inlineCode.95"],"code":"`formatDuration()`"},{"id":"/root/children/77/children/96","type":"text","loc":{"start":21032,"end":21034,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":105,"e":107}},"dim":["","paragraph.77","text.96"],"code":", "},{"id":"/root/children/77/children/97","type":"inlineCode","loc":{"start":21034,"end":21044,"line":{"s":397,"e":397,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |"]},"column":{"s":107,"e":117}},"dim":["","paragraph.77","inlineCode.97"],"code":"`logMsg()`"},{"id":"/root/children/77/children/98","type":"text","loc":{"start":21044,"end":21142,"line":{"s":397,"e":398,"code":["| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |","| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":117,"e":2}},"dim":["","paragraph.77","text.98"],"code":"      | Common utils used everywhere                                                          |\n| "},{"id":"/root/children/77/children/99","type":"strong","loc":{"start":21142,"end":21152,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.77","strong.99"],"code":"**String**"},{"id":"/root/children/77/children/99/children/0","type":"text","loc":{"start":21144,"end":21150,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":4,"e":10}},"dim":["","paragraph.77","strong.99","text.0"],"code":"String"},{"id":"/root/children/77/children/100","type":"text","loc":{"start":21152,"end":21164,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":12,"e":24}},"dim":["","paragraph.77","text.100"],"code":"          | "},{"id":"/root/children/77/children/101","type":"inlineCode","loc":{"start":21164,"end":21174,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":24,"e":34}},"dim":["","paragraph.77","inlineCode.101"],"code":"`string.*`"},{"id":"/root/children/77/children/102","type":"text","loc":{"start":21174,"end":21265,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":34,"e":125}},"dim":["","paragraph.77","text.102"],"code":" (namespace)                                                                             | "},{"id":"/root/children/77/children/103","type":"inlineCode","loc":{"start":21265,"end":21283,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":125,"e":143}},"dim":["","paragraph.77","inlineCode.103"],"code":"`ConvertToAscii()`"},{"id":"/root/children/77/children/104","type":"text","loc":{"start":21283,"end":21285,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":143,"e":145}},"dim":["","paragraph.77","text.104"],"code":", "},{"id":"/root/children/77/children/105","type":"inlineCode","loc":{"start":21285,"end":21305,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":145,"e":165}},"dim":["","paragraph.77","inlineCode.105"],"code":"`commonPathPrefix()`"},{"id":"/root/children/77/children/106","type":"text","loc":{"start":21305,"end":21307,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":165,"e":167}},"dim":["","paragraph.77","text.106"],"code":", "},{"id":"/root/children/77/children/107","type":"inlineCode","loc":{"start":21307,"end":21317,"line":{"s":398,"e":398,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |"]},"column":{"s":167,"e":177}},"dim":["","paragraph.77","inlineCode.107"],"code":"`uuidv4()`"},{"id":"/root/children/77/children/108","type":"text","loc":{"start":21317,"end":21355,"line":{"s":398,"e":399,"code":["| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |","| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":177,"e":2}},"dim":["","paragraph.77","text.108"],"code":"                                  |\n| "},{"id":"/root/children/77/children/109","type":"strong","loc":{"start":21355,"end":21366,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":2,"e":13}},"dim":["","paragraph.77","strong.109"],"code":"**Parsing**"},{"id":"/root/children/77/children/109/children/0","type":"text","loc":{"start":21357,"end":21364,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":4,"e":11}},"dim":["","paragraph.77","strong.109","text.0"],"code":"Parsing"},{"id":"/root/children/77/children/110","type":"text","loc":{"start":21366,"end":21377,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":13,"e":24}},"dim":["","paragraph.77","text.110"],"code":"         | "},{"id":"/root/children/77/children/111","type":"inlineCode","loc":{"start":21377,"end":21389,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":24,"e":36}},"dim":["","paragraph.77","inlineCode.111"],"code":"`mddToAst()`"},{"id":"/root/children/77/children/112","type":"text","loc":{"start":21389,"end":21391,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":36,"e":38}},"dim":["","paragraph.77","text.112"],"code":", "},{"id":"/root/children/77/children/113","type":"inlineCode","loc":{"start":21391,"end":21398,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":38,"e":45}},"dim":["","paragraph.77","inlineCode.113"],"code":"`acorn`"},{"id":"/root/children/77/children/114","type":"text","loc":{"start":21398,"end":21400,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":45,"e":47}},"dim":["","paragraph.77","text.114"],"code":", "},{"id":"/root/children/77/children/115","type":"inlineCode","loc":{"start":21400,"end":21406,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":47,"e":53}},"dim":["","paragraph.77","inlineCode.115"],"code":"`sexp`"},{"id":"/root/children/77/children/116","type":"text","loc":{"start":21406,"end":21408,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":53,"e":55}},"dim":["","paragraph.77","text.116"],"code":", "},{"id":"/root/children/77/children/117","type":"inlineCode","loc":{"start":21408,"end":21414,"line":{"s":399,"e":399,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |"]},"column":{"s":55,"e":61}},"dim":["","paragraph.77","inlineCode.117"],"code":"`glob`"},{"id":"/root/children/77/children/118","type":"text","loc":{"start":21414,"end":21568,"line":{"s":399,"e":400,"code":["| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |","| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":61,"e":2}},"dim":["","paragraph.77","text.118"],"code":"                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| "},{"id":"/root/children/77/children/119","type":"strong","loc":{"start":21568,"end":21582,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.77","strong.119"],"code":"**Components**"},{"id":"/root/children/77/children/119/children/0","type":"text","loc":{"start":21570,"end":21580,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":4,"e":14}},"dim":["","paragraph.77","strong.119","text.0"],"code":"Components"},{"id":"/root/children/77/children/120","type":"text","loc":{"start":21582,"end":21590,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":16,"e":24}},"dim":["","paragraph.77","text.120"],"code":"      | "},{"id":"/root/children/77/children/121","type":"inlineCode","loc":{"start":21590,"end":21600,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":24,"e":34}},"dim":["","paragraph.77","inlineCode.121"],"code":"`ObjectId`"},{"id":"/root/children/77/children/122","type":"text","loc":{"start":21600,"end":21602,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":34,"e":36}},"dim":["","paragraph.77","text.122"],"code":", "},{"id":"/root/children/77/children/123","type":"inlineCode","loc":{"start":21602,"end":21609,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":36,"e":43}},"dim":["","paragraph.77","inlineCode.123"],"code":"`Larry`"},{"id":"/root/children/77/children/124","type":"text","loc":{"start":21609,"end":21611,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":43,"e":45}},"dim":["","paragraph.77","text.124"],"code":", "},{"id":"/root/children/77/children/125","type":"inlineCode","loc":{"start":21611,"end":21625,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":45,"e":59}},"dim":["","paragraph.77","inlineCode.125"],"code":"`RemoteSqlite`"},{"id":"/root/children/77/children/126","type":"text","loc":{"start":21625,"end":21627,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":59,"e":61}},"dim":["","paragraph.77","text.126"],"code":", "},{"id":"/root/children/77/children/127","type":"inlineCode","loc":{"start":21627,"end":21640,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":61,"e":74}},"dim":["","paragraph.77","inlineCode.127"],"code":"`RemoteState`"},{"id":"/root/children/77/children/128","type":"text","loc":{"start":21640,"end":21642,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":74,"e":76}},"dim":["","paragraph.77","text.128"],"code":", "},{"id":"/root/children/77/children/129","type":"inlineCode","loc":{"start":21642,"end":21656,"line":{"s":400,"e":400,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |"]},"column":{"s":76,"e":90}},"dim":["","paragraph.77","inlineCode.129"],"code":"`initDropZone`"},{"id":"/root/children/77/children/130","type":"text","loc":{"start":21656,"end":21781,"line":{"s":400,"e":401,"code":["| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |","| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":90,"e":2}},"dim":["","paragraph.77","text.130"],"code":"                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| "},{"id":"/root/children/77/children/131","type":"strong","loc":{"start":21781,"end":21800,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":2,"e":21}},"dim":["","paragraph.77","strong.131"],"code":"**Custom Elements**"},{"id":"/root/children/77/children/131/children/0","type":"text","loc":{"start":21783,"end":21798,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":4,"e":19}},"dim":["","paragraph.77","strong.131","text.0"],"code":"Custom Elements"},{"id":"/root/children/77/children/132","type":"text","loc":{"start":21800,"end":21803,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":21,"e":24}},"dim":["","paragraph.77","text.132"],"code":" | "},{"id":"/root/children/77/children/133","type":"inlineCode","loc":{"start":21803,"end":21811,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":24,"e":32}},"dim":["","paragraph.77","inlineCode.133"],"code":"`S4Unit`"},{"id":"/root/children/77/children/134","type":"text","loc":{"start":21811,"end":21813,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":32,"e":34}},"dim":["","paragraph.77","text.134"],"code":", "},{"id":"/root/children/77/children/135","type":"inlineCode","loc":{"start":21813,"end":21821,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":34,"e":42}},"dim":["","paragraph.77","inlineCode.135"],"code":"`S4Json`"},{"id":"/root/children/77/children/136","type":"text","loc":{"start":21821,"end":21823,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":42,"e":44}},"dim":["","paragraph.77","text.136"],"code":", "},{"id":"/root/children/77/children/137","type":"inlineCode","loc":{"start":21823,"end":21831,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":44,"e":52}},"dim":["","paragraph.77","inlineCode.137"],"code":"`S4Vega`"},{"id":"/root/children/77/children/138","type":"text","loc":{"start":21831,"end":21833,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":52,"e":54}},"dim":["","paragraph.77","text.138"],"code":", "},{"id":"/root/children/77/children/139","type":"inlineCode","loc":{"start":21833,"end":21841,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":54,"e":62}},"dim":["","paragraph.77","inlineCode.139"],"code":"`S4Tint`"},{"id":"/root/children/77/children/140","type":"text","loc":{"start":21841,"end":21942,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":62,"e":163}},"dim":["","paragraph.77","text.140"],"code":"                                                             | Web components registered at boot via "},{"id":"/root/children/77/children/141","type":"inlineCode","loc":{"start":21942,"end":21964,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":163,"e":185}},"dim":["","paragraph.77","inlineCode.141"],"code":"`initCustomElements()`"},{"id":"/root/children/77/children/142","type":"text","loc":{"start":21964,"end":21991,"line":{"s":401,"e":401,"code":["| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |"]},"column":{"s":185,"e":212}},"dim":["","paragraph.77","text.142"],"code":"                          |"},{"id":"/root/children/78","type":"heading","loc":{"start":21993,"end":22013,"line":{"s":403,"e":403,"code":["### ssss Sub-modules"]},"column":{"s":0,"e":20}},"dim":["","heading.78"],"code":"### ssss Sub-modules","symbName":"heading","symbRange":[22015,23173],"symbRangeL":[403,427],"outerCode":"\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```","outerHtml":"\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>"},{"id":"/root/children/78/children/0","type":"text","loc":{"start":21997,"end":22013,"line":{"s":403,"e":403,"code":["### ssss Sub-modules"]},"column":{"s":4,"e":20}},"dim":["","heading.78","text.0"],"code":"ssss Sub-modules"},{"id":"/root/children/79","type":"code","loc":{"start":22015,"end":23173,"line":{"s":405,"e":426,"code":["```","ssss.js","  |-- modules/","  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4","  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)","  |     |-- glob.js            : globToRegExp","  |     |-- sexp.js            : S-expression parser","  |     |-- acorn.min.js       : JS expression parser (bundled)","  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips","  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)","  |-- adapters/","  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)","  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)","  |-- components/","  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)","  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete","  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)","  |     |-- s4-json.js         : S4Json web component + jsonToHtml()","  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)","  |     |-- s4-tint.js         : S4Tint web component (Tint templates)","  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)","```"]},"column":{"s":0,"e":3}},"dim":["","code.79"],"code":"```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```","symbName":"code","symbRange":[23175,23211],"symbRangeL":[null,429],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>"},{"id":"/root/children/80","type":"heading","loc":{"start":23175,"end":23211,"line":{"s":428,"e":428,"code":["### How ssss.bootS4() drives the app"]},"column":{"s":0,"e":36}},"dim":["","heading.80"],"code":"### How ssss.bootS4() drives the app","symbName":"heading","symbRange":[23213,23796],"symbRangeL":[428,442],"outerCode":"\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.","outerHtml":"\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>"},{"id":"/root/children/80/children/0","type":"text","loc":{"start":23179,"end":23211,"line":{"s":428,"e":428,"code":["### How ssss.bootS4() drives the app"]},"column":{"s":4,"e":36}},"dim":["","heading.80","text.0"],"code":"How ssss.bootS4() drives the app"},{"id":"/root/children/81","type":"code","loc":{"start":23213,"end":23679,"line":{"s":430,"e":439,"code":["```","bootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)","  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)","  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)","  3. Wait for document.readyState === 'complete'","  4. Register page routes (if any)","  5. Call fnBootStart() -> player's boot callback","  6. Router.init(null, routeFallback)  -> hash change handler","  7. Router.navigate(defaultRoute)     -> first render","```"]},"column":{"s":0,"e":3}},"dim":["","code.81"],"code":"```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```","symbName":"code","symbRange":[23681,24466],"symbRangeL":[null,451],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>"},{"id":"/root/children/82","type":"paragraph","loc":{"start":23681,"end":23796,"line":{"s":441,"e":441,"code":["The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`."]},"column":{"s":0,"e":115}},"dim":["","paragraph.82"],"code":"The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`."},{"id":"/root/children/82/children/0","type":"text","loc":{"start":23681,"end":23685,"line":{"s":441,"e":441,"code":["The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`."]},"column":{"s":0,"e":4}},"dim":["","paragraph.82","text.0"],"code":"The "},{"id":"/root/children/82/children/1","type":"inlineCode","loc":{"start":23685,"end":23700,"line":{"s":441,"e":441,"code":["The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`."]},"column":{"s":4,"e":19}},"dim":["","paragraph.82","inlineCode.1"],"code":"`routeFallback`"},{"id":"/root/children/82/children/2","type":"text","loc":{"start":23700,"end":23776,"line":{"s":441,"e":441,"code":["The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`."]},"column":{"s":19,"e":95}},"dim":["","paragraph.82","text.2"],"code":" is where player.js receives every hash change as parsed tokens, then calls "},{"id":"/root/children/82/children/3","type":"inlineCode","loc":{"start":23776,"end":23795,"line":{"s":441,"e":441,"code":["The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`."]},"column":{"s":95,"e":114}},"dim":["","paragraph.82","inlineCode.3"],"code":"`renderLayerMode()`"},{"id":"/root/children/82/children/4","type":"text","loc":{"start":23795,"end":23796,"line":{"s":441,"e":441,"code":["The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`."]},"column":{"s":114,"e":115}},"dim":["","paragraph.82","text.4"],"code":"."},{"id":"/root/children/83","type":"heading","loc":{"start":23798,"end":23818,"line":{"s":443,"e":443,"code":["### Key design notes"]},"column":{"s":0,"e":20}},"dim":["","heading.83"],"code":"### Key design notes","symbName":"heading","symbRange":[23820,24449],"symbRangeL":[443,449],"outerCode":"\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.","outerHtml":"\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>"},{"id":"/root/children/83/children/0","type":"text","loc":{"start":23802,"end":23818,"line":{"s":443,"e":443,"code":["### Key design notes"]},"column":{"s":4,"e":20}},"dim":["","heading.83","text.0"],"code":"Key design notes"},{"id":"/root/children/84","type":"list","loc":{"start":23820,"end":24449,"line":{"s":445,"e":448,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.","- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.","- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.","- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":0,"e":139}},"dim":["","list.84"],"code":"- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.","symbName":"list","symbRange":[24451,32905],"symbRangeL":[445,563],"outerCode":"- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:","outerHtml":"<ul><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>"},{"id":"/root/children/84/children/0","type":"listItem","loc":{"start":23820,"end":23950,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":0,"e":130}},"dim":["","list.84","listItem.0"],"code":"- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."},{"id":"/root/children/84/children/0/children/0","type":"paragraph","loc":{"start":23822,"end":23950,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":2,"e":130}},"dim":["","list.84","listItem.0","paragraph.0"],"code":"**ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."},{"id":"/root/children/84/children/0/children/0/children/0","type":"strong","loc":{"start":23822,"end":23838,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":2,"e":18}},"dim":["","list.84","listItem.0","paragraph.0","strong.0"],"code":"**ETag caching**"},{"id":"/root/children/84/children/0/children/0/children/0/children/0","type":"text","loc":{"start":23824,"end":23836,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":4,"e":16}},"dim":["","list.84","listItem.0","paragraph.0","strong.0","text.0"],"code":"ETag caching"},{"id":"/root/children/84/children/0/children/0/children/1","type":"text","loc":{"start":23838,"end":23840,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":18,"e":20}},"dim":["","list.84","listItem.0","paragraph.0","text.1"],"code":": "},{"id":"/root/children/84/children/0/children/0/children/2","type":"inlineCode","loc":{"start":23840,"end":23855,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":20,"e":35}},"dim":["","list.84","listItem.0","paragraph.0","inlineCode.2"],"code":"`fetchWithETag`"},{"id":"/root/children/84/children/0/children/0/children/3","type":"text","loc":{"start":23855,"end":23909,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":35,"e":89}},"dim":["","list.84","listItem.0","paragraph.0","text.3"],"code":" stores response bodies + ETags in localStorage. Uses "},{"id":"/root/children/84/children/0/children/0/children/4","type":"inlineCode","loc":{"start":23909,"end":23924,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":89,"e":104}},"dim":["","list.84","listItem.0","paragraph.0","inlineCode.4"],"code":"`If-None-Match`"},{"id":"/root/children/84/children/0/children/0/children/5","type":"text","loc":{"start":23924,"end":23950,"line":{"s":445,"e":445,"code":["- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses."]},"column":{"s":104,"e":130}},"dim":["","list.84","listItem.0","paragraph.0","text.5"],"code":" header for 304 responses."},{"id":"/root/children/84/children/1","type":"listItem","loc":{"start":23951,"end":24093,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":0,"e":142}},"dim":["","list.84","listItem.1"],"code":"- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."},{"id":"/root/children/84/children/1/children/0","type":"paragraph","loc":{"start":23953,"end":24093,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":2,"e":142}},"dim":["","list.84","listItem.1","paragraph.0"],"code":"**Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."},{"id":"/root/children/84/children/1/children/0/children/0","type":"strong","loc":{"start":23953,"end":23972,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":2,"e":21}},"dim":["","list.84","listItem.1","paragraph.0","strong.0"],"code":"**Protocol router**"},{"id":"/root/children/84/children/1/children/0/children/0/children/0","type":"text","loc":{"start":23955,"end":23970,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":4,"e":19}},"dim":["","list.84","listItem.1","paragraph.0","strong.0","text.0"],"code":"Protocol router"},{"id":"/root/children/84/children/1/children/0/children/1","type":"text","loc":{"start":23972,"end":23974,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":21,"e":23}},"dim":["","list.84","listItem.1","paragraph.0","text.1"],"code":": "},{"id":"/root/children/84/children/1/children/0/children/2","type":"inlineCode","loc":{"start":23974,"end":23981,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":23,"e":30}},"dim":["","list.84","listItem.1","paragraph.0","inlineCode.2"],"code":"`Url()`"},{"id":"/root/children/84/children/1/children/0/children/3","type":"text","loc":{"start":23981,"end":24008,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":30,"e":57}},"dim":["","list.84","listItem.1","paragraph.0","text.3"],"code":" translates abstract URIs ("},{"id":"/root/children/84/children/1/children/0/children/4","type":"inlineCode","loc":{"start":24008,"end":24019,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":57,"e":68}},"dim":["","list.84","listItem.1","paragraph.0","inlineCode.4"],"code":"`assets://`"},{"id":"/root/children/84/children/1/children/0/children/5","type":"text","loc":{"start":24019,"end":24021,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":68,"e":70}},"dim":["","list.84","listItem.1","paragraph.0","text.5"],"code":", "},{"id":"/root/children/84/children/1/children/0/children/6","type":"inlineCode","loc":{"start":24021,"end":24030,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":70,"e":79}},"dim":["","list.84","listItem.1","paragraph.0","inlineCode.6"],"code":"`repo://`"},{"id":"/root/children/84/children/1/children/0/children/7","type":"text","loc":{"start":24030,"end":24093,"line":{"s":446,"e":446,"code":["- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html."]},"column":{"s":79,"e":142}},"dim":["","list.84","listItem.1","paragraph.0","text.7"],"code":") to real URLs based on environment config injected by jq-html."},{"id":"/root/children/84/children/2","type":"listItem","loc":{"start":24094,"end":24309,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":0,"e":215}},"dim":["","list.84","listItem.2"],"code":"- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."},{"id":"/root/children/84/children/2/children/0","type":"paragraph","loc":{"start":24096,"end":24309,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":2,"e":215}},"dim":["","list.84","listItem.2","paragraph.0"],"code":"**Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."},{"id":"/root/children/84/children/2/children/0/children/0","type":"strong","loc":{"start":24096,"end":24123,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":2,"e":29}},"dim":["","list.84","listItem.2","paragraph.0","strong.0"],"code":"**Tracked event listeners**"},{"id":"/root/children/84/children/2/children/0/children/0/children/0","type":"text","loc":{"start":24098,"end":24121,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":4,"e":27}},"dim":["","list.84","listItem.2","paragraph.0","strong.0","text.0"],"code":"Tracked event listeners"},{"id":"/root/children/84/children/2/children/0/children/1","type":"text","loc":{"start":24123,"end":24125,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":29,"e":31}},"dim":["","list.84","listItem.2","paragraph.0","text.1"],"code":": "},{"id":"/root/children/84/children/2/children/0/children/2","type":"inlineCode","loc":{"start":24125,"end":24150,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":31,"e":56}},"dim":["","list.84","listItem.2","paragraph.0","inlineCode.2"],"code":"`addTrackedEventListener`"},{"id":"/root/children/84/children/2/children/0/children/3","type":"text","loc":{"start":24150,"end":24172,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":56,"e":78}},"dim":["","list.84","listItem.2","paragraph.0","text.3"],"code":" stores references in "},{"id":"/root/children/84/children/2/children/0/children/4","type":"inlineCode","loc":{"start":24172,"end":24200,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":78,"e":106}},"dim":["","list.84","listItem.2","paragraph.0","inlineCode.4"],"code":"`element.__listenerRegistry`"},{"id":"/root/children/84/children/2/children/0/children/5","type":"text","loc":{"start":24200,"end":24204,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":106,"e":110}},"dim":["","list.84","listItem.2","paragraph.0","text.5"],"code":" so "},{"id":"/root/children/84/children/2/children/0/children/6","type":"inlineCode","loc":{"start":24204,"end":24226,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":110,"e":132}},"dim":["","list.84","listItem.2","paragraph.0","inlineCode.6"],"code":"`removeEventListeners`"},{"id":"/root/children/84/children/2/children/0/children/7","type":"text","loc":{"start":24226,"end":24309,"line":{"s":447,"e":447,"code":["- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."]},"column":{"s":132,"e":215}},"dim":["","list.84","listItem.2","paragraph.0","text.7"],"code":" can batch-remove them. This is how layer switching cleans up hotkeys and tooltips."},{"id":"/root/children/84/children/3","type":"listItem","loc":{"start":24310,"end":24449,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":0,"e":139}},"dim":["","list.84","listItem.3"],"code":"- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."},{"id":"/root/children/84/children/3/children/0","type":"paragraph","loc":{"start":24312,"end":24449,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":2,"e":139}},"dim":["","list.84","listItem.3","paragraph.0"],"code":"**window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."},{"id":"/root/children/84/children/3/children/0/children/0","type":"strong","loc":{"start":24312,"end":24329,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":2,"e":19}},"dim":["","list.84","listItem.3","paragraph.0","strong.0"],"code":"**window.s4util**"},{"id":"/root/children/84/children/3/children/0/children/0/children/0","type":"text","loc":{"start":24314,"end":24327,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":4,"e":17}},"dim":["","list.84","listItem.3","paragraph.0","strong.0","text.0"],"code":"window.s4util"},{"id":"/root/children/84/children/3/children/0/children/1","type":"text","loc":{"start":24329,"end":24362,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":19,"e":52}},"dim":["","list.84","listItem.3","paragraph.0","text.1"],"code":": global object set at boot with "},{"id":"/root/children/84/children/3/children/0/children/2","type":"inlineCode","loc":{"start":24362,"end":24378,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":52,"e":68}},"dim":["","list.84","listItem.3","paragraph.0","inlineCode.2"],"code":"`protocolRouter`"},{"id":"/root/children/84/children/3/children/0/children/3","type":"text","loc":{"start":24378,"end":24380,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":68,"e":70}},"dim":["","list.84","listItem.3","paragraph.0","text.3"],"code":", "},{"id":"/root/children/84/children/3/children/0/children/4","type":"inlineCode","loc":{"start":24380,"end":24384,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":70,"e":74}},"dim":["","list.84","listItem.3","paragraph.0","inlineCode.4"],"code":"`ea`"},{"id":"/root/children/84/children/3/children/0/children/5","type":"text","loc":{"start":24384,"end":24449,"line":{"s":448,"e":448,"code":["- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules."]},"column":{"s":74,"e":139}},"dim":["","list.84","listItem.3","paragraph.0","text.5"],"code":" (EventAggregator), and fetch helpers. Shared across all modules."},{"id":"/root/children/85","type":"heading","loc":{"start":24451,"end":24466,"line":{"s":450,"e":450,"code":["## Architecture"]},"column":{"s":0,"e":15}},"dim":["","heading.85"],"code":"## Architecture","symbName":"heading","symbRange":[24468,26941],"symbRangeL":[450,491],"outerCode":"\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```","outerHtml":"\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>"},{"id":"/root/children/85/children/0","type":"text","loc":{"start":24454,"end":24466,"line":{"s":450,"e":450,"code":["## Architecture"]},"column":{"s":3,"e":15}},"dim":["","heading.85","text.0"],"code":"Architecture"},{"id":"/root/children/86","type":"code","loc":{"start":24468,"end":26941,"line":{"s":452,"e":490,"code":["```","player.js (orchestrator)","  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh","  |-- glass-search.js      : autocomplete search engine","  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core","  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)","  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)","  |     |-- search/filter-files-pre.js   : SQL pre-filter for files","  |     |-- search/filter-maker.js       : older duplicate of filter-base","  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite","  |     |-- search/search-history.js     : in-memory history search","  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter","  |     |-- search/search-space.js       : empty","  |-- larry.js             : grammar-based autocomplete actor system","  |-- navigation.js        : RBush spatial nav + DOM selection helpers","  |-- commands.js          : @command handlers (denied, login, demo)","  |-- codeditor.js         : CodeMirror 6 editor wrapper","  |-- tabler.js            : table heatmap + DataTable integration","  |-- visualization.js     : Vega-Lite + intensity timeline renderer","  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar","  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)","  |-- editor-flash.js      : CM6 flash highlight extension","  |-- meditation-navigation.js : editor cursor history tracker","  |-- actions.js           : shared window.* action registrations","  |-- player/              : split modules","  |     |-- map-show.js          : OpenLayers map for space view","  |     |-- page-dashboard.js     : dashboard view with word cloud + tables","  |     |-- page-file-manager.js  : file manager actions","  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys","  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper","  |     |-- player-state.js       : resetMachine, STATE init","  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile","  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete","  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo","  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers","  |     |-- player-edit.js        : renderEdit, renderSave","  |     |-- player-space.js       : renderSpace, resetSpace","  |     |-- simple-datatables.js : DataTables library (bundled)","```"]},"column":{"s":0,"e":3}},"dim":["","code.86"],"code":"```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```","symbName":"code","symbRange":[26943,38869],"symbRangeL":[null,665],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>"},{"id":"/root/children/87","type":"heading","loc":{"start":26943,"end":26962,"line":{"s":492,"e":492,"code":["## Layers (routing)"]},"column":{"s":0,"e":19}},"dim":["","heading.87"],"code":"## Layers (routing)","symbName":"heading","symbRange":[26964,27411],"symbRangeL":[492,501],"outerCode":"\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |","outerHtml":"\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>"},{"id":"/root/children/87/children/0","type":"text","loc":{"start":26946,"end":26962,"line":{"s":492,"e":492,"code":["## Layers (routing)"]},"column":{"s":3,"e":19}},"dim":["","heading.87","text.0"],"code":"Layers (routing)"},{"id":"/root/children/88","type":"paragraph","loc":{"start":26964,"end":27411,"line":{"s":494,"e":500,"code":["| Hash prefix | Layer | Description                           |","| ----------- | ----- | ------------------------------------- |","| `#/glass/`  | glass | Search/command overlay                |","| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |","| `#/edit/`   | edit  | CodeMirror 6 editor                   |","| `#/space/`  | space | OpenLayers 2D map viewer              |","| `#/save/`   | save  | Triggers paper save via webhook       |"]},"column":{"s":0,"e":63}},"dim":["","paragraph.88"],"code":"| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |"},{"id":"/root/children/88/children/0","type":"text","loc":{"start":26964,"end":27094,"line":{"s":494,"e":496,"code":["| Hash prefix | Layer | Description                           |","| ----------- | ----- | ------------------------------------- |","| `#/glass/`  | glass | Search/command overlay                |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.88","text.0"],"code":"| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| "},{"id":"/root/children/88/children/1","type":"inlineCode","loc":{"start":27094,"end":27104,"line":{"s":496,"e":496,"code":["| `#/glass/`  | glass | Search/command overlay                |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.88","inlineCode.1"],"code":"`#/glass/`"},{"id":"/root/children/88/children/2","type":"text","loc":{"start":27104,"end":27158,"line":{"s":496,"e":497,"code":["| `#/glass/`  | glass | Search/command overlay                |","| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |"]},"column":{"s":12,"e":2}},"dim":["","paragraph.88","text.2"],"code":"  | glass | Search/command overlay                |\n| "},{"id":"/root/children/88/children/3","type":"inlineCode","loc":{"start":27158,"end":27168,"line":{"s":497,"e":497,"code":["| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.88","inlineCode.3"],"code":"`#/paper/`"},{"id":"/root/children/88/children/4","type":"text","loc":{"start":27168,"end":27222,"line":{"s":497,"e":498,"code":["| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |","| `#/edit/`   | edit  | CodeMirror 6 editor                   |"]},"column":{"s":12,"e":2}},"dim":["","paragraph.88","text.4"],"code":"  | paper | Document viewer (mdd, md, html, json) |\n| "},{"id":"/root/children/88/children/5","type":"inlineCode","loc":{"start":27222,"end":27231,"line":{"s":498,"e":498,"code":["| `#/edit/`   | edit  | CodeMirror 6 editor                   |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.88","inlineCode.5"],"code":"`#/edit/`"},{"id":"/root/children/88/children/6","type":"text","loc":{"start":27231,"end":27286,"line":{"s":498,"e":499,"code":["| `#/edit/`   | edit  | CodeMirror 6 editor                   |","| `#/space/`  | space | OpenLayers 2D map viewer              |"]},"column":{"s":11,"e":2}},"dim":["","paragraph.88","text.6"],"code":"   | edit  | CodeMirror 6 editor                   |\n| "},{"id":"/root/children/88/children/7","type":"inlineCode","loc":{"start":27286,"end":27296,"line":{"s":499,"e":499,"code":["| `#/space/`  | space | OpenLayers 2D map viewer              |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.88","inlineCode.7"],"code":"`#/space/`"},{"id":"/root/children/88/children/8","type":"text","loc":{"start":27296,"end":27350,"line":{"s":499,"e":500,"code":["| `#/space/`  | space | OpenLayers 2D map viewer              |","| `#/save/`   | save  | Triggers paper save via webhook       |"]},"column":{"s":12,"e":2}},"dim":["","paragraph.88","text.8"],"code":"  | space | OpenLayers 2D map viewer              |\n| "},{"id":"/root/children/88/children/9","type":"inlineCode","loc":{"start":27350,"end":27359,"line":{"s":500,"e":500,"code":["| `#/save/`   | save  | Triggers paper save via webhook       |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.88","inlineCode.9"],"code":"`#/save/`"},{"id":"/root/children/88/children/10","type":"text","loc":{"start":27359,"end":27411,"line":{"s":500,"e":500,"code":["| `#/save/`   | save  | Triggers paper save via webhook       |"]},"column":{"s":11,"e":63}},"dim":["","paragraph.88","text.10"],"code":"   | save  | Triggers paper save via webhook       |"},{"id":"/root/children/89","type":"heading","loc":{"start":27413,"end":27430,"line":{"s":502,"e":502,"code":["## File Summaries"]},"column":{"s":0,"e":17}},"dim":["","heading.89"],"code":"## File Summaries","symbName":"heading","symbRange":[27432,31709],"symbRangeL":[502,550],"outerCode":"\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |","outerHtml":"\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>"},{"id":"/root/children/89/children/0","type":"text","loc":{"start":27416,"end":27430,"line":{"s":502,"e":502,"code":["## File Summaries"]},"column":{"s":3,"e":17}},"dim":["","heading.89","text.0"],"code":"File Summaries"},{"id":"/root/children/90","type":"paragraph","loc":{"start":27432,"end":31709,"line":{"s":504,"e":549,"code":["| File                            | Lines | Role                                           |","| ------------------------------- | ----- | ---------------------------------------------- |","| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |","| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |","| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |","| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |","| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |","| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |","| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |","| home.jq.json                    | ~75   | Config: minimal mode                           |","| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |","| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |","| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |","| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |","| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |","| commands.js                     | ~75   | @denied, @login, @demo command handlers        |","| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |","| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |","| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |","| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |","| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |","| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |","| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |","| actions.js                      | ~100  | Shared window.\\* action registrations          |","| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |","| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |","| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |","| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |","| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |","| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |","| search/search-history.js        | ~50   | History search by term matching                |","| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |","| search/search-space.js          | 1     | Empty                                          |","| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |","| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |","| player/page-file-manager.js     | ~52   | File manager actions                           |","| player/player-edit.js           | ~388  | renderEdit, renderSave                         |","| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |","| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |","| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |","| player/player-space.js          | ~264  | renderSpace, resetSpace                        |","| player/player-state.js          | ~69   | resetMachine, STATE init                       |","| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |","| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |","| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |","| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |"]},"column":{"s":0,"e":92}},"dim":["","paragraph.90"],"code":"| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |"},{"id":"/root/children/90/children/0","type":"text","loc":{"start":27432,"end":27960,"line":{"s":504,"e":509,"code":["| File                            | Lines | Role                                           |","| ------------------------------- | ----- | ---------------------------------------------- |","| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |","| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |","| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |","| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |"]},"column":{"s":0,"e":63}},"dim":["","paragraph.90","text.0"],"code":"| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, "},{"id":"/root/children/90/children/1","type":"inlineCode","loc":{"start":27960,"end":27978,"line":{"s":509,"e":509,"code":["| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |"]},"column":{"s":63,"e":81}},"dim":["","paragraph.90","inlineCode.1"],"code":"`repolero:reflect`"},{"id":"/root/children/90/children/2","type":"text","loc":{"start":27978,"end":31709,"line":{"s":509,"e":549,"code":["| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |","| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |","| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |","| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |","| home.jq.json                    | ~75   | Config: minimal mode                           |","| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |","| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |","| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |","| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |","| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |","| commands.js                     | ~75   | @denied, @login, @demo command handlers        |","| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |","| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |","| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |","| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |","| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |","| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |","| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |","| actions.js                      | ~100  | Shared window.\\* action registrations          |","| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |","| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |","| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |","| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |","| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |","| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |","| search/search-history.js        | ~50   | History search by term matching                |","| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |","| search/search-space.js          | 1     | Empty                                          |","| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |","| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |","| player/page-file-manager.js     | ~52   | File manager actions                           |","| player/player-edit.js           | ~388  | renderEdit, renderSave                         |","| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |","| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |","| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |","| player/player-space.js          | ~264  | renderSpace, resetSpace                        |","| player/player-state.js          | ~69   | resetMachine, STATE init                       |","| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |","| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |","| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |","| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |"]},"column":{"s":81,"e":92}},"dim":["","paragraph.90","text.2"],"code":" title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |"},{"id":"/root/children/91","type":"heading","loc":{"start":31711,"end":31748,"line":{"s":551,"e":551,"code":["## Layout Engines (Eisenhower Matrix)"]},"column":{"s":0,"e":37}},"dim":["","heading.91"],"code":"## Layout Engines (Eisenhower Matrix)","symbName":"heading","symbRange":[31750,33113],"symbRangeL":[551,566],"outerCode":"\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel","outerHtml":"\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>"},{"id":"/root/children/91/children/0","type":"text","loc":{"start":31714,"end":31748,"line":{"s":551,"e":551,"code":["## Layout Engines (Eisenhower Matrix)"]},"column":{"s":3,"e":37}},"dim":["","heading.91","text.0"],"code":"Layout Engines (Eisenhower Matrix)"},{"id":"/root/children/92","type":"paragraph","loc":{"start":31750,"end":32829,"line":{"s":553,"e":560,"code":["| Layout                                       | Key Distinction                                                                     |","| -------------------------------------------- | ----------------------------------------------------------------------------------- |","| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |","| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |","| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |","| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |","| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |","| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"]},"column":{"s":0,"e":134}},"dim":["","paragraph.92"],"code":"| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"},{"id":"/root/children/92/children/0","type":"text","loc":{"start":31750,"end":32022,"line":{"s":553,"e":555,"code":["| Layout                                       | Key Distinction                                                                     |","| -------------------------------------------- | ----------------------------------------------------------------------------------- |","| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.92","text.0"],"code":"| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/92/children/1","type":"strong","loc":{"start":32022,"end":32032,"line":{"s":555,"e":555,"code":["| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.92","strong.1"],"code":"**normal**"},{"id":"/root/children/92/children/1/children/0","type":"text","loc":{"start":32024,"end":32030,"line":{"s":555,"e":555,"code":["| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |"]},"column":{"s":4,"e":10}},"dim":["","paragraph.92","strong.1","text.0"],"code":"normal"},{"id":"/root/children/92/children/2","type":"text","loc":{"start":32032,"end":32034,"line":{"s":555,"e":555,"code":["| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |"]},"column":{"s":12,"e":14}},"dim":["","paragraph.92","text.2"],"code":" ("},{"id":"/root/children/92/children/3","type":"inlineCode","loc":{"start":32034,"end":32065,"line":{"s":555,"e":555,"code":["| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |"]},"column":{"s":14,"e":45}},"dim":["","paragraph.92","inlineCode.3"],"code":"`layout-tasks-in-box-normal.js`"},{"id":"/root/children/92/children/4","type":"text","loc":{"start":32065,"end":32125,"line":{"s":555,"e":555,"code":["| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |"]},"column":{"s":45,"e":105}},"dim":["","paragraph.92","text.4"],"code":") | Simple row-packer, corner-mirror gravity, now also uses "},{"id":"/root/children/92/children/5","type":"inlineCode","loc":{"start":32125,"end":32135,"line":{"s":555,"e":555,"code":["| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |"]},"column":{"s":105,"e":115}},"dim":["","paragraph.92","inlineCode.5"],"code":"`topoSort`"},{"id":"/root/children/92/children/6","type":"text","loc":{"start":32135,"end":32157,"line":{"s":555,"e":556,"code":["| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |","| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |"]},"column":{"s":115,"e":2}},"dim":["","paragraph.92","text.6"],"code":"                  |\n| "},{"id":"/root/children/92/children/7","type":"strong","loc":{"start":32157,"end":32165,"line":{"s":556,"e":556,"code":["| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.92","strong.7"],"code":"**boss**"},{"id":"/root/children/92/children/7/children/0","type":"text","loc":{"start":32159,"end":32163,"line":{"s":556,"e":556,"code":["| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |"]},"column":{"s":4,"e":8}},"dim":["","paragraph.92","strong.7","text.0"],"code":"boss"},{"id":"/root/children/92/children/8","type":"text","loc":{"start":32165,"end":32167,"line":{"s":556,"e":556,"code":["| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |"]},"column":{"s":10,"e":12}},"dim":["","paragraph.92","text.8"],"code":" ("},{"id":"/root/children/92/children/9","type":"inlineCode","loc":{"start":32167,"end":32183,"line":{"s":556,"e":556,"code":["| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |"]},"column":{"s":12,"e":28}},"dim":["","paragraph.92","inlineCode.9"],"code":"`like-a-boss.js`"},{"id":"/root/children/92/children/10","type":"text","loc":{"start":32183,"end":32292,"line":{"s":556,"e":557,"code":["| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |","| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |"]},"column":{"s":28,"e":2}},"dim":["","paragraph.92","text.10"],"code":")                  | Force-directed spring+repulsion physics                                             |\n| "},{"id":"/root/children/92/children/11","type":"strong","loc":{"start":32292,"end":32301,"line":{"s":557,"e":557,"code":["| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.92","strong.11"],"code":"**boss2**"},{"id":"/root/children/92/children/11/children/0","type":"text","loc":{"start":32294,"end":32299,"line":{"s":557,"e":557,"code":["| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.92","strong.11","text.0"],"code":"boss2"},{"id":"/root/children/92/children/12","type":"text","loc":{"start":32301,"end":32303,"line":{"s":557,"e":557,"code":["| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |"]},"column":{"s":11,"e":13}},"dim":["","paragraph.92","text.12"],"code":" ("},{"id":"/root/children/92/children/13","type":"inlineCode","loc":{"start":32303,"end":32320,"line":{"s":557,"e":557,"code":["| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |"]},"column":{"s":13,"e":30}},"dim":["","paragraph.92","inlineCode.13"],"code":"`like-a-boss2.js`"},{"id":"/root/children/92/children/14","type":"text","loc":{"start":32320,"end":32366,"line":{"s":557,"e":557,"code":["| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |"]},"column":{"s":30,"e":76}},"dim":["","paragraph.92","text.14"],"code":")                | Deterministic grid packer, "},{"id":"/root/children/92/children/15","type":"inlineCode","loc":{"start":32366,"end":32376,"line":{"s":557,"e":557,"code":["| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |"]},"column":{"s":76,"e":86}},"dim":["","paragraph.92","inlineCode.15"],"code":"`topoSort`"},{"id":"/root/children/92/children/16","type":"text","loc":{"start":32376,"end":32427,"line":{"s":557,"e":558,"code":["| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |","| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |"]},"column":{"s":86,"e":2}},"dim":["","paragraph.92","text.16"],"code":" direction-aware                               |\n| "},{"id":"/root/children/92/children/17","type":"strong","loc":{"start":32427,"end":32436,"line":{"s":558,"e":558,"code":["| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.92","strong.17"],"code":"**boss3**"},{"id":"/root/children/92/children/17/children/0","type":"text","loc":{"start":32429,"end":32434,"line":{"s":558,"e":558,"code":["| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.92","strong.17","text.0"],"code":"boss3"},{"id":"/root/children/92/children/18","type":"text","loc":{"start":32436,"end":32438,"line":{"s":558,"e":558,"code":["| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |"]},"column":{"s":11,"e":13}},"dim":["","paragraph.92","text.18"],"code":" ("},{"id":"/root/children/92/children/19","type":"inlineCode","loc":{"start":32438,"end":32455,"line":{"s":558,"e":558,"code":["| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |"]},"column":{"s":13,"e":30}},"dim":["","paragraph.92","inlineCode.19"],"code":"`like-a-boss3.js`"},{"id":"/root/children/92/children/20","type":"text","loc":{"start":32455,"end":32562,"line":{"s":558,"e":559,"code":["| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |","| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |"]},"column":{"s":30,"e":2}},"dim":["","paragraph.92","text.20"],"code":")                | ngraph.forcelayout physics engine                                                   |\n| "},{"id":"/root/children/92/children/21","type":"strong","loc":{"start":32562,"end":32571,"line":{"s":559,"e":559,"code":["| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.92","strong.21"],"code":"**boss4**"},{"id":"/root/children/92/children/21/children/0","type":"text","loc":{"start":32564,"end":32569,"line":{"s":559,"e":559,"code":["| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.92","strong.21","text.0"],"code":"boss4"},{"id":"/root/children/92/children/22","type":"text","loc":{"start":32571,"end":32573,"line":{"s":559,"e":559,"code":["| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |"]},"column":{"s":11,"e":13}},"dim":["","paragraph.92","text.22"],"code":" ("},{"id":"/root/children/92/children/23","type":"inlineCode","loc":{"start":32573,"end":32590,"line":{"s":559,"e":559,"code":["| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |"]},"column":{"s":13,"e":30}},"dim":["","paragraph.92","inlineCode.23"],"code":"`like-a-boss4.js`"},{"id":"/root/children/92/children/24","type":"text","loc":{"start":32590,"end":32697,"line":{"s":559,"e":560,"code":["| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |","| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"]},"column":{"s":30,"e":2}},"dim":["","paragraph.92","text.24"],"code":")                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| "},{"id":"/root/children/92/children/25","type":"strong","loc":{"start":32697,"end":32706,"line":{"s":560,"e":560,"code":["| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.92","strong.25"],"code":"**boss5**"},{"id":"/root/children/92/children/25/children/0","type":"text","loc":{"start":32699,"end":32704,"line":{"s":560,"e":560,"code":["| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"]},"column":{"s":4,"e":9}},"dim":["","paragraph.92","strong.25","text.0"],"code":"boss5"},{"id":"/root/children/92/children/26","type":"text","loc":{"start":32706,"end":32708,"line":{"s":560,"e":560,"code":["| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"]},"column":{"s":11,"e":13}},"dim":["","paragraph.92","text.26"],"code":" ("},{"id":"/root/children/92/children/27","type":"inlineCode","loc":{"start":32708,"end":32725,"line":{"s":560,"e":560,"code":["| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"]},"column":{"s":13,"e":30}},"dim":["","paragraph.92","inlineCode.27"],"code":"`like-a-boss5.js`"},{"id":"/root/children/92/children/28","type":"text","loc":{"start":32725,"end":32829,"line":{"s":560,"e":560,"code":["| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"]},"column":{"s":30,"e":134}},"dim":["","paragraph.92","text.28"],"code":")                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |"},{"id":"/root/children/93","type":"paragraph","loc":{"start":32831,"end":32905,"line":{"s":562,"e":562,"code":["All active layout engines (boss2–5, normal) set two width fields per task:"]},"column":{"s":0,"e":74}},"dim":["","paragraph.93"],"code":"All active layout engines (boss2–5, normal) set two width fields per task:"},{"id":"/root/children/93/children/0","type":"text","loc":{"start":32831,"end":32905,"line":{"s":562,"e":562,"code":["All active layout engines (boss2–5, normal) set two width fields per task:"]},"column":{"s":0,"e":74}},"dim":["","paragraph.93","text.0"],"code":"All active layout engines (boss2–5, normal) set two width fields per task:"},{"id":"/root/children/94","type":"list","loc":{"start":32907,"end":33113,"line":{"s":564,"e":565,"code":["- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only","- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"]},"column":{"s":0,"e":125}},"dim":["","list.94"],"code":"- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel","symbName":"list","symbRange":[33115,33465],"symbRangeL":[564,578],"outerCode":"- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:","outerHtml":"<ul><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>"},{"id":"/root/children/94/children/0","type":"listItem","loc":{"start":32907,"end":32987,"line":{"s":564,"e":564,"code":["- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"]},"column":{"s":0,"e":80}},"dim":["","list.94","listItem.0"],"code":"- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"},{"id":"/root/children/94/children/0/children/0","type":"paragraph","loc":{"start":32909,"end":32987,"line":{"s":564,"e":564,"code":["- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"]},"column":{"s":2,"e":80}},"dim":["","list.94","listItem.0","paragraph.0"],"code":"`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"},{"id":"/root/children/94/children/0/children/0/children/0","type":"inlineCode","loc":{"start":32909,"end":32920,"line":{"s":564,"e":564,"code":["- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"]},"column":{"s":2,"e":13}},"dim":["","list.94","listItem.0","paragraph.0","inlineCode.0"],"code":"`taskWidth`"},{"id":"/root/children/94/children/0/children/0/children/1","type":"text","loc":{"start":32920,"end":32923,"line":{"s":564,"e":564,"code":["- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"]},"column":{"s":13,"e":16}},"dim":["","list.94","listItem.0","paragraph.0","text.1"],"code":" = "},{"id":"/root/children/94/children/0/children/0/children/2","type":"inlineCode","loc":{"start":32923,"end":32970,"line":{"s":564,"e":564,"code":["- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"]},"column":{"s":16,"e":63}},"dim":["","list.94","listItem.0","paragraph.0","inlineCode.2"],"code":"`estimateTextWidth(task, fontSize, fontWeight)`"},{"id":"/root/children/94/children/0/children/0/children/3","type":"text","loc":{"start":32970,"end":32987,"line":{"s":564,"e":564,"code":["- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only"]},"column":{"s":63,"e":80}},"dim":["","list.94","listItem.0","paragraph.0","text.3"],"code":" — task text only"},{"id":"/root/children/94/children/1","type":"listItem","loc":{"start":32988,"end":33113,"line":{"s":565,"e":565,"code":["- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"]},"column":{"s":0,"e":125}},"dim":["","list.94","listItem.1"],"code":"- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"},{"id":"/root/children/94/children/1/children/0","type":"paragraph","loc":{"start":32990,"end":33113,"line":{"s":565,"e":565,"code":["- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"]},"column":{"s":2,"e":125}},"dim":["","list.94","listItem.1","paragraph.0"],"code":"`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"},{"id":"/root/children/94/children/1/children/0/children/0","type":"inlineCode","loc":{"start":32990,"end":32997,"line":{"s":565,"e":565,"code":["- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"]},"column":{"s":2,"e":9}},"dim":["","list.94","listItem.1","paragraph.0","inlineCode.0"],"code":"`width`"},{"id":"/root/children/94/children/1/children/0/children/1","type":"text","loc":{"start":32997,"end":33000,"line":{"s":565,"e":565,"code":["- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"]},"column":{"s":9,"e":12}},"dim":["","list.94","listItem.1","paragraph.0","text.1"],"code":" = "},{"id":"/root/children/94/children/1/children/0/children/2","type":"inlineCode","loc":{"start":33000,"end":33085,"line":{"s":565,"e":565,"code":["- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"]},"column":{"s":12,"e":97}},"dim":["","list.94","listItem.1","paragraph.0","inlineCode.2"],"code":"`estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)`"},{"id":"/root/children/94/children/1/children/0/children/3","type":"text","loc":{"start":33085,"end":33113,"line":{"s":565,"e":565,"code":["- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel"]},"column":{"s":97,"e":125}},"dim":["","list.94","listItem.1","paragraph.0","text.3"],"code":" — task + optional miniLabel"},{"id":"/root/children/95","type":"heading","loc":{"start":33115,"end":33138,"line":{"s":567,"e":567,"code":["## Found TASK: Comments"]},"column":{"s":0,"e":23}},"dim":["","heading.95"],"code":"## Found TASK: Comments","symbName":"heading","symbRange":[33140,33387],"symbRangeL":[567,574],"outerCode":"\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.","outerHtml":"\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>"},{"id":"/root/children/95/children/0","type":"text","loc":{"start":33118,"end":33138,"line":{"s":567,"e":567,"code":["## Found TASK: Comments"]},"column":{"s":3,"e":23}},"dim":["","heading.95","text.0"],"code":"Found TASK: Comments"},{"id":"/root/children/96","type":"paragraph","loc":{"start":33140,"end":33328,"line":{"s":569,"e":571,"code":["| File       | Line | Task                                   |","| ---------- | ---- | -------------------------------------- |","| rc.jq.html | ~219 | Empty `// TASK:` in execState function |"]},"column":{"s":0,"e":62}},"dim":["","paragraph.96"],"code":"| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |"},{"id":"/root/children/96/children/0","type":"text","loc":{"start":33140,"end":33294,"line":{"s":569,"e":571,"code":["| File       | Line | Task                                   |","| ---------- | ---- | -------------------------------------- |","| rc.jq.html | ~219 | Empty `// TASK:` in execState function |"]},"column":{"s":0,"e":28}},"dim":["","paragraph.96","text.0"],"code":"| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty "},{"id":"/root/children/96/children/1","type":"inlineCode","loc":{"start":33294,"end":33304,"line":{"s":571,"e":571,"code":["| rc.jq.html | ~219 | Empty `// TASK:` in execState function |"]},"column":{"s":28,"e":38}},"dim":["","paragraph.96","inlineCode.1"],"code":"`// TASK:`"},{"id":"/root/children/96/children/2","type":"text","loc":{"start":33304,"end":33328,"line":{"s":571,"e":571,"code":["| rc.jq.html | ~219 | Empty `// TASK:` in execState function |"]},"column":{"s":38,"e":62}},"dim":["","paragraph.96","text.2"],"code":" in execState function |"},{"id":"/root/children/97","type":"paragraph","loc":{"start":33330,"end":33387,"line":{"s":573,"e":573,"code":["Note: The autoZoom TASK in map-show.js has been resolved."]},"column":{"s":0,"e":57}},"dim":["","paragraph.97"],"code":"Note: The autoZoom TASK in map-show.js has been resolved."},{"id":"/root/children/97/children/0","type":"text","loc":{"start":33330,"end":33387,"line":{"s":573,"e":573,"code":["Note: The autoZoom TASK in map-show.js has been resolved."]},"column":{"s":0,"e":57}},"dim":["","paragraph.97","text.0"],"code":"Note: The autoZoom TASK in map-show.js has been resolved."},{"id":"/root/children/98","type":"heading","loc":{"start":33389,"end":33407,"line":{"s":575,"e":575,"code":["## Theme structure"]},"column":{"s":0,"e":18}},"dim":["","heading.98"],"code":"## Theme structure","symbName":"heading","symbRange":[33409,34095],"symbRangeL":[575,584],"outerCode":"\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.","outerHtml":"\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>"},{"id":"/root/children/98/children/0","type":"text","loc":{"start":33392,"end":33407,"line":{"s":575,"e":575,"code":["## Theme structure"]},"column":{"s":3,"e":18}},"dim":["","heading.98","text.0"],"code":"Theme structure"},{"id":"/root/children/99","type":"paragraph","loc":{"start":33409,"end":33465,"line":{"s":577,"e":577,"code":["Two LESS files serve the same HTML with different skins:"]},"column":{"s":0,"e":56}},"dim":["","paragraph.99"],"code":"Two LESS files serve the same HTML with different skins:"},{"id":"/root/children/99/children/0","type":"text","loc":{"start":33409,"end":33465,"line":{"s":577,"e":577,"code":["Two LESS files serve the same HTML with different skins:"]},"column":{"s":0,"e":56}},"dim":["","paragraph.99","text.0"],"code":"Two LESS files serve the same HTML with different skins:"},{"id":"/root/children/100","type":"list","loc":{"start":33467,"end":33727,"line":{"s":579,"e":581,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)","- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)","  Both import `theme-bare/repolero-effect.less`."]},"column":{"s":0,"e":48}},"dim":["","list.100"],"code":"- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.","symbName":"list","symbRange":[33729,34422],"symbRangeL":[579,590],"outerCode":"- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations","outerHtml":"<ul><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>"},{"id":"/root/children/100/children/0","type":"listItem","loc":{"start":33467,"end":33594,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":0,"e":127}},"dim":["","list.100","listItem.0"],"code":"- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"},{"id":"/root/children/100/children/0/children/0","type":"paragraph","loc":{"start":33469,"end":33594,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":2,"e":127}},"dim":["","list.100","listItem.0","paragraph.0"],"code":"`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"},{"id":"/root/children/100/children/0/children/0/children/0","type":"inlineCode","loc":{"start":33469,"end":33495,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":2,"e":28}},"dim":["","list.100","listItem.0","paragraph.0","inlineCode.0"],"code":"`theme-bare/repolero.less`"},{"id":"/root/children/100/children/0/children/0/children/1","type":"text","loc":{"start":33495,"end":33533,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":28,"e":66}},"dim":["","list.100","listItem.0","paragraph.0","text.1"],"code":" — full-featured \"view\" skin (used by "},{"id":"/root/children/100/children/0/children/0/children/2","type":"inlineCode","loc":{"start":33533,"end":33547,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":66,"e":80}},"dim":["","list.100","listItem.0","paragraph.0","inlineCode.2"],"code":"`view.jq.html`"},{"id":"/root/children/100/children/0/children/0/children/3","type":"text","loc":{"start":33547,"end":33549,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":80,"e":82}},"dim":["","list.100","listItem.0","paragraph.0","text.3"],"code":", "},{"id":"/root/children/100/children/0/children/0/children/4","type":"inlineCode","loc":{"start":33549,"end":33563,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":82,"e":96}},"dim":["","list.100","listItem.0","paragraph.0","inlineCode.4"],"code":"`root.jq.html`"},{"id":"/root/children/100/children/0/children/0/children/5","type":"text","loc":{"start":33563,"end":33565,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":96,"e":98}},"dim":["","list.100","listItem.0","paragraph.0","text.5"],"code":", "},{"id":"/root/children/100/children/0/children/0/children/6","type":"inlineCode","loc":{"start":33565,"end":33579,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":98,"e":112}},"dim":["","list.100","listItem.0","paragraph.0","inlineCode.6"],"code":"`home.jq.html`"},{"id":"/root/children/100/children/0/children/0/children/7","type":"text","loc":{"start":33579,"end":33581,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":112,"e":114}},"dim":["","list.100","listItem.0","paragraph.0","text.7"],"code":", "},{"id":"/root/children/100/children/0/children/0/children/8","type":"inlineCode","loc":{"start":33581,"end":33593,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":114,"e":126}},"dim":["","list.100","listItem.0","paragraph.0","inlineCode.8"],"code":"`rc.jq.html`"},{"id":"/root/children/100/children/0/children/0/children/9","type":"text","loc":{"start":33593,"end":33594,"line":{"s":579,"e":579,"code":["- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)"]},"column":{"s":126,"e":127}},"dim":["","list.100","listItem.0","paragraph.0","text.9"],"code":")"},{"id":"/root/children/100/children/1","type":"listItem","loc":{"start":33595,"end":33727,"line":{"s":580,"e":581,"code":["- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)","  Both import `theme-bare/repolero-effect.less`."]},"column":{"s":0,"e":48}},"dim":["","list.100","listItem.1"],"code":"- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`."},{"id":"/root/children/100/children/1/children/0","type":"paragraph","loc":{"start":33597,"end":33727,"line":{"s":580,"e":581,"code":["- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)","  Both import `theme-bare/repolero-effect.less`."]},"column":{"s":2,"e":48}},"dim":["","list.100","listItem.1","paragraph.0"],"code":"`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`."},{"id":"/root/children/100/children/1/children/0/children/0","type":"inlineCode","loc":{"start":33597,"end":33625,"line":{"s":580,"e":580,"code":["- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)"]},"column":{"s":2,"e":30}},"dim":["","list.100","listItem.1","paragraph.0","inlineCode.0"],"code":"`theme-bare/reflection.less`"},{"id":"/root/children/100/children/1/children/0/children/1","type":"text","loc":{"start":33625,"end":33660,"line":{"s":580,"e":580,"code":["- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)"]},"column":{"s":30,"e":65}},"dim":["","list.100","listItem.1","paragraph.0","text.1"],"code":" — compact \"reflect\" skin (used by "},{"id":"/root/children/100/children/1/children/0/children/2","type":"inlineCode","loc":{"start":33660,"end":33677,"line":{"s":580,"e":580,"code":["- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)"]},"column":{"s":65,"e":82}},"dim":["","list.100","listItem.1","paragraph.0","inlineCode.2"],"code":"`reflect.jq.html`"},{"id":"/root/children/100/children/1/children/0/children/3","type":"text","loc":{"start":33677,"end":33693,"line":{"s":580,"e":581,"code":["- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)","  Both import `theme-bare/repolero-effect.less`."]},"column":{"s":82,"e":14}},"dim":["","list.100","listItem.1","paragraph.0","text.3"],"code":")\n  Both import "},{"id":"/root/children/100/children/1/children/0/children/4","type":"inlineCode","loc":{"start":33693,"end":33726,"line":{"s":581,"e":581,"code":["  Both import `theme-bare/repolero-effect.less`."]},"column":{"s":14,"e":47}},"dim":["","list.100","listItem.1","paragraph.0","inlineCode.4"],"code":"`theme-bare/repolero-effect.less`"},{"id":"/root/children/100/children/1/children/0/children/5","type":"text","loc":{"start":33726,"end":33727,"line":{"s":581,"e":581,"code":["  Both import `theme-bare/repolero-effect.less`."]},"column":{"s":47,"e":48}},"dim":["","list.100","listItem.1","paragraph.0","text.5"],"code":"."},{"id":"/root/children/101","type":"paragraph","loc":{"start":33729,"end":34095,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":0,"e":366}},"dim":["","paragraph.101"],"code":"**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."},{"id":"/root/children/101/children/0","type":"strong","loc":{"start":33729,"end":33768,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":0,"e":39}},"dim":["","paragraph.101","strong.0"],"code":"**Key structural difference resolved:**"},{"id":"/root/children/101/children/0/children/0","type":"text","loc":{"start":33731,"end":33766,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":2,"e":37}},"dim":["","paragraph.101","strong.0","text.0"],"code":"Key structural difference resolved:"},{"id":"/root/children/101/children/1","type":"text","loc":{"start":33768,"end":33769,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":39,"e":40}},"dim":["","paragraph.101","text.1"],"code":" "},{"id":"/root/children/101/children/2","type":"inlineCode","loc":{"start":33769,"end":33786,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":40,"e":57}},"dim":["","paragraph.101","inlineCode.2"],"code":"`reflection.less`"},{"id":"/root/children/101/children/3","type":"text","loc":{"start":33786,"end":33803,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":57,"e":74}},"dim":["","paragraph.101","text.3"],"code":" now defines the "},{"id":"/root/children/101/children/4","type":"inlineCode","loc":{"start":33803,"end":33826,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":74,"e":97}},"dim":["","paragraph.101","inlineCode.4"],"code":"`.normalizeMddNormal()`"},{"id":"/root/children/101/children/5","type":"text","loc":{"start":33826,"end":33842,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":97,"e":113}},"dim":["","paragraph.101","text.5"],"code":" mixin (same as "},{"id":"/root/children/101/children/6","type":"inlineCode","loc":{"start":33842,"end":33857,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":113,"e":128}},"dim":["","paragraph.101","inlineCode.6"],"code":"`repolero.less`"},{"id":"/root/children/101/children/7","type":"text","loc":{"start":33857,"end":33880,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":128,"e":151}},"dim":["","paragraph.101","text.7"],"code":") and calls it in both "},{"id":"/root/children/101/children/8","type":"inlineCode","loc":{"start":33880,"end":33888,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":151,"e":159}},"dim":["","paragraph.101","inlineCode.8"],"code":"`#paper`"},{"id":"/root/children/101/children/9","type":"text","loc":{"start":33888,"end":33893,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":159,"e":164}},"dim":["","paragraph.101","text.9"],"code":" and "},{"id":"/root/children/101/children/10","type":"inlineCode","loc":{"start":33893,"end":33925,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":164,"e":196}},"dim":["","paragraph.101","inlineCode.10"],"code":"`dialog.s4-dialog article.paper`"},{"id":"/root/children/101/children/11","type":"text","loc":{"start":33925,"end":34095,"line":{"s":583,"e":583,"code":["**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."]},"column":{"s":196,"e":366}},"dim":["","paragraph.101","text.11"],"code":" contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles."},{"id":"/root/children/102","type":"heading","loc":{"start":34097,"end":34121,"line":{"s":585,"e":585,"code":["### Dialog backdrop blur"]},"column":{"s":0,"e":24}},"dim":["","heading.102"],"code":"### Dialog backdrop blur","symbName":"heading","symbRange":[34123,34405],"symbRangeL":[585,588],"outerCode":"\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).","outerHtml":"\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>"},{"id":"/root/children/102/children/0","type":"text","loc":{"start":34101,"end":34121,"line":{"s":585,"e":585,"code":["### Dialog backdrop blur"]},"column":{"s":4,"e":24}},"dim":["","heading.102","text.0"],"code":"Dialog backdrop blur"},{"id":"/root/children/103","type":"paragraph","loc":{"start":34123,"end":34405,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":0,"e":282}},"dim":["","paragraph.103"],"code":"`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."},{"id":"/root/children/103/children/0","type":"inlineCode","loc":{"start":34123,"end":34140,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":0,"e":17}},"dim":["","paragraph.103","inlineCode.0"],"code":"`water-hack.less`"},{"id":"/root/children/103/children/1","type":"text","loc":{"start":34140,"end":34161,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":17,"e":38}},"dim":["","paragraph.103","text.1"],"code":" (line 1524) applies "},{"id":"/root/children/103/children/2","type":"inlineCode","loc":{"start":34161,"end":34189,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":38,"e":66}},"dim":["","paragraph.103","inlineCode.2"],"code":"`backdrop-filter: blur(4px)`"},{"id":"/root/children/103/children/3","type":"text","loc":{"start":34189,"end":34193,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":66,"e":70}},"dim":["","paragraph.103","text.3"],"code":" to "},{"id":"/root/children/103/children/4","type":"inlineCode","loc":{"start":34193,"end":34213,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":70,"e":90}},"dim":["","paragraph.103","inlineCode.4"],"code":"`<dialog>::backdrop`"},{"id":"/root/children/103/children/5","type":"text","loc":{"start":34213,"end":34224,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":90,"e":101}},"dim":["","paragraph.103","text.5"],"code":". When any "},{"id":"/root/children/103/children/6","type":"inlineCode","loc":{"start":34224,"end":34234,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":101,"e":111}},"dim":["","paragraph.103","inlineCode.6"],"code":"`<dialog>`"},{"id":"/root/children/103/children/7","type":"text","loc":{"start":34234,"end":34245,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":111,"e":122}},"dim":["","paragraph.103","text.7"],"code":" opens via "},{"id":"/root/children/103/children/8","type":"inlineCode","loc":{"start":34245,"end":34259,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":122,"e":136}},"dim":["","paragraph.103","inlineCode.8"],"code":"`.showModal()`"},{"id":"/root/children/103/children/9","type":"text","loc":{"start":34259,"end":34307,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":136,"e":184}},"dim":["","paragraph.103","text.9"],"code":", the background behind it is blurred + dimmed ("},{"id":"/root/children/103/children/10","type":"inlineCode","loc":{"start":34307,"end":34318,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":184,"e":195}},"dim":["","paragraph.103","inlineCode.10"],"code":"`#0000009c`"},{"id":"/root/children/103/children/11","type":"text","loc":{"start":34318,"end":34344,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":195,"e":221}},"dim":["","paragraph.103","text.11"],"code":"). This covers all native "},{"id":"/root/children/103/children/12","type":"inlineCode","loc":{"start":34344,"end":34354,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":221,"e":231}},"dim":["","paragraph.103","inlineCode.12"],"code":"`<dialog>`"},{"id":"/root/children/103/children/13","type":"text","loc":{"start":34354,"end":34405,"line":{"s":587,"e":587,"code":["`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.)."]},"column":{"s":231,"e":282}},"dim":["","paragraph.103","text.13"],"code":" elements (campaign select, heading-changes, etc.)."},{"id":"/root/children/104","type":"heading","loc":{"start":34407,"end":34422,"line":{"s":589,"e":589,"code":["## Observations"]},"column":{"s":0,"e":15}},"dim":["","heading.104"],"code":"## Observations","symbName":"heading","symbRange":[34424,35636],"symbRangeL":[589,605],"outerCode":"\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---","outerHtml":"\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>"},{"id":"/root/children/104/children/0","type":"text","loc":{"start":34410,"end":34422,"line":{"s":589,"e":589,"code":["## Observations"]},"column":{"s":3,"e":15}},"dim":["","heading.104","text.0"],"code":"Observations"},{"id":"/root/children/105","type":"list","loc":{"start":34424,"end":35631,"line":{"s":591,"e":602,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.","2. **PROXY/proxy pattern** — see detailed analysis below.","3. **Commented-out code** - present in player.js and ui-touchbar.js.","4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.","5. **search/search-space.js** is empty.","6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.","7. **New file**: `player/page-file-manager.js` - file manager actions module.","8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).","9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).","10. **One TASK found** in rc.jq.html (empty execState function)","11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".","12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":0,"e":206}},"dim":["","list.105"],"code":"1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.","symbName":"list","symbRange":[35633,35687],"symbRangeL":[591,609],"outerCode":"2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern","outerHtml":"<ol><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>"},{"id":"/root/children/105/children/0","type":"listItem","loc":{"start":34424,"end":34525,"line":{"s":591,"e":591,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."]},"column":{"s":0,"e":101}},"dim":["","list.105","listItem.0"],"code":"1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."},{"id":"/root/children/105/children/0/children/0","type":"paragraph","loc":{"start":34427,"end":34525,"line":{"s":591,"e":591,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."]},"column":{"s":3,"e":101}},"dim":["","list.105","listItem.0","paragraph.0"],"code":"**player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."},{"id":"/root/children/105/children/0/children/0/children/0","type":"strong","loc":{"start":34427,"end":34449,"line":{"s":591,"e":591,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."]},"column":{"s":3,"e":25}},"dim":["","list.105","listItem.0","paragraph.0","strong.0"],"code":"**player.js is large**"},{"id":"/root/children/105/children/0/children/0/children/0/children/0","type":"text","loc":{"start":34429,"end":34447,"line":{"s":591,"e":591,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."]},"column":{"s":5,"e":23}},"dim":["","list.105","listItem.0","paragraph.0","strong.0","text.0"],"code":"player.js is large"},{"id":"/root/children/105/children/0/children/0/children/1","type":"text","loc":{"start":34449,"end":34465,"line":{"s":591,"e":591,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."]},"column":{"s":25,"e":41}},"dim":["","list.105","listItem.0","paragraph.0","text.1"],"code":" (~1022 lines). "},{"id":"/root/children/105/children/0/children/0/children/2","type":"inlineCode","loc":{"start":34465,"end":34482,"line":{"s":591,"e":591,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."]},"column":{"s":41,"e":58}},"dim":["","list.105","listItem.0","paragraph.0","inlineCode.2"],"code":"`renderLayerMode`"},{"id":"/root/children/105/children/0/children/0/children/3","type":"text","loc":{"start":34482,"end":34525,"line":{"s":591,"e":591,"code":["1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch."]},"column":{"s":58,"e":101}},"dim":["","list.105","listItem.0","paragraph.0","text.3"],"code":" handles all layer switching in one switch."},{"id":"/root/children/105/children/1","type":"listItem","loc":{"start":34526,"end":34583,"line":{"s":592,"e":592,"code":["2. **PROXY/proxy pattern** — see detailed analysis below."]},"column":{"s":0,"e":57}},"dim":["","list.105","listItem.1"],"code":"2. **PROXY/proxy pattern** — see detailed analysis below."},{"id":"/root/children/105/children/1/children/0","type":"paragraph","loc":{"start":34529,"end":34583,"line":{"s":592,"e":592,"code":["2. **PROXY/proxy pattern** — see detailed analysis below."]},"column":{"s":3,"e":57}},"dim":["","list.105","listItem.1","paragraph.0"],"code":"**PROXY/proxy pattern** — see detailed analysis below."},{"id":"/root/children/105/children/1/children/0/children/0","type":"strong","loc":{"start":34529,"end":34552,"line":{"s":592,"e":592,"code":["2. **PROXY/proxy pattern** — see detailed analysis below."]},"column":{"s":3,"e":26}},"dim":["","list.105","listItem.1","paragraph.0","strong.0"],"code":"**PROXY/proxy pattern**"},{"id":"/root/children/105/children/1/children/0/children/0/children/0","type":"text","loc":{"start":34531,"end":34550,"line":{"s":592,"e":592,"code":["2. **PROXY/proxy pattern** — see detailed analysis below."]},"column":{"s":5,"e":24}},"dim":["","list.105","listItem.1","paragraph.0","strong.0","text.0"],"code":"PROXY/proxy pattern"},{"id":"/root/children/105/children/1/children/0/children/1","type":"text","loc":{"start":34552,"end":34583,"line":{"s":592,"e":592,"code":["2. **PROXY/proxy pattern** — see detailed analysis below."]},"column":{"s":26,"e":57}},"dim":["","list.105","listItem.1","paragraph.0","text.1"],"code":" — see detailed analysis below."},{"id":"/root/children/105/children/2","type":"listItem","loc":{"start":34584,"end":34652,"line":{"s":593,"e":593,"code":["3. **Commented-out code** - present in player.js and ui-touchbar.js."]},"column":{"s":0,"e":68}},"dim":["","list.105","listItem.2"],"code":"3. **Commented-out code** - present in player.js and ui-touchbar.js."},{"id":"/root/children/105/children/2/children/0","type":"paragraph","loc":{"start":34587,"end":34652,"line":{"s":593,"e":593,"code":["3. **Commented-out code** - present in player.js and ui-touchbar.js."]},"column":{"s":3,"e":68}},"dim":["","list.105","listItem.2","paragraph.0"],"code":"**Commented-out code** - present in player.js and ui-touchbar.js."},{"id":"/root/children/105/children/2/children/0/children/0","type":"strong","loc":{"start":34587,"end":34609,"line":{"s":593,"e":593,"code":["3. **Commented-out code** - present in player.js and ui-touchbar.js."]},"column":{"s":3,"e":25}},"dim":["","list.105","listItem.2","paragraph.0","strong.0"],"code":"**Commented-out code**"},{"id":"/root/children/105/children/2/children/0/children/0/children/0","type":"text","loc":{"start":34589,"end":34607,"line":{"s":593,"e":593,"code":["3. **Commented-out code** - present in player.js and ui-touchbar.js."]},"column":{"s":5,"e":23}},"dim":["","list.105","listItem.2","paragraph.0","strong.0","text.0"],"code":"Commented-out code"},{"id":"/root/children/105/children/2/children/0/children/1","type":"text","loc":{"start":34609,"end":34652,"line":{"s":593,"e":593,"code":["3. **Commented-out code** - present in player.js and ui-touchbar.js."]},"column":{"s":25,"e":68}},"dim":["","list.105","listItem.2","paragraph.0","text.1"],"code":" - present in player.js and ui-touchbar.js."},{"id":"/root/children/105/children/3","type":"listItem","loc":{"start":34653,"end":34740,"line":{"s":594,"e":594,"code":["4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic."]},"column":{"s":0,"e":87}},"dim":["","list.105","listItem.3"],"code":"4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic."},{"id":"/root/children/105/children/3/children/0","type":"paragraph","loc":{"start":34656,"end":34740,"line":{"s":594,"e":594,"code":["4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic."]},"column":{"s":3,"e":87}},"dim":["","list.105","listItem.3","paragraph.0"],"code":"**search/filter-maker.js** appears to be an older duplicate of filter-base.js logic."},{"id":"/root/children/105/children/3/children/0/children/0","type":"strong","loc":{"start":34656,"end":34682,"line":{"s":594,"e":594,"code":["4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic."]},"column":{"s":3,"e":29}},"dim":["","list.105","listItem.3","paragraph.0","strong.0"],"code":"**search/filter-maker.js**"},{"id":"/root/children/105/children/3/children/0/children/0/children/0","type":"text","loc":{"start":34658,"end":34680,"line":{"s":594,"e":594,"code":["4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic."]},"column":{"s":5,"e":27}},"dim":["","list.105","listItem.3","paragraph.0","strong.0","text.0"],"code":"search/filter-maker.js"},{"id":"/root/children/105/children/3/children/0/children/1","type":"text","loc":{"start":34682,"end":34740,"line":{"s":594,"e":594,"code":["4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic."]},"column":{"s":29,"e":87}},"dim":["","list.105","listItem.3","paragraph.0","text.1"],"code":" appears to be an older duplicate of filter-base.js logic."},{"id":"/root/children/105/children/4","type":"listItem","loc":{"start":34741,"end":34780,"line":{"s":595,"e":595,"code":["5. **search/search-space.js** is empty."]},"column":{"s":0,"e":39}},"dim":["","list.105","listItem.4"],"code":"5. **search/search-space.js** is empty."},{"id":"/root/children/105/children/4/children/0","type":"paragraph","loc":{"start":34744,"end":34780,"line":{"s":595,"e":595,"code":["5. **search/search-space.js** is empty."]},"column":{"s":3,"e":39}},"dim":["","list.105","listItem.4","paragraph.0"],"code":"**search/search-space.js** is empty."},{"id":"/root/children/105/children/4/children/0/children/0","type":"strong","loc":{"start":34744,"end":34770,"line":{"s":595,"e":595,"code":["5. **search/search-space.js** is empty."]},"column":{"s":3,"e":29}},"dim":["","list.105","listItem.4","paragraph.0","strong.0"],"code":"**search/search-space.js**"},{"id":"/root/children/105/children/4/children/0/children/0/children/0","type":"text","loc":{"start":34746,"end":34768,"line":{"s":595,"e":595,"code":["5. **search/search-space.js** is empty."]},"column":{"s":5,"e":27}},"dim":["","list.105","listItem.4","paragraph.0","strong.0","text.0"],"code":"search/search-space.js"},{"id":"/root/children/105/children/4/children/0/children/1","type":"text","loc":{"start":34770,"end":34780,"line":{"s":595,"e":595,"code":["5. **search/search-space.js** is empty."]},"column":{"s":29,"e":39}},"dim":["","list.105","listItem.4","paragraph.0","text.1"],"code":" is empty."},{"id":"/root/children/105/children/5","type":"listItem","loc":{"start":34781,"end":34865,"line":{"s":596,"e":596,"code":["6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."]},"column":{"s":0,"e":84}},"dim":["","list.105","listItem.5"],"code":"6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."},{"id":"/root/children/105/children/5/children/0","type":"paragraph","loc":{"start":34784,"end":34865,"line":{"s":596,"e":596,"code":["6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."]},"column":{"s":3,"e":84}},"dim":["","list.105","listItem.5","paragraph.0"],"code":"**New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."},{"id":"/root/children/105/children/5/children/0/children/0","type":"strong","loc":{"start":34784,"end":34796,"line":{"s":596,"e":596,"code":["6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."]},"column":{"s":3,"e":15}},"dim":["","list.105","listItem.5","paragraph.0","strong.0"],"code":"**New file**"},{"id":"/root/children/105/children/5/children/0/children/0/children/0","type":"text","loc":{"start":34786,"end":34794,"line":{"s":596,"e":596,"code":["6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."]},"column":{"s":5,"e":13}},"dim":["","list.105","listItem.5","paragraph.0","strong.0","text.0"],"code":"New file"},{"id":"/root/children/105/children/5/children/0/children/1","type":"text","loc":{"start":34796,"end":34798,"line":{"s":596,"e":596,"code":["6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."]},"column":{"s":15,"e":17}},"dim":["","list.105","listItem.5","paragraph.0","text.1"],"code":": "},{"id":"/root/children/105/children/5/children/0/children/2","type":"inlineCode","loc":{"start":34798,"end":34816,"line":{"s":596,"e":596,"code":["6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."]},"column":{"s":17,"e":35}},"dim":["","list.105","listItem.5","paragraph.0","inlineCode.2"],"code":"`alpine-stores.js`"},{"id":"/root/children/105/children/5/children/0/children/3","type":"text","loc":{"start":34816,"end":34865,"line":{"s":596,"e":596,"code":["6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode."]},"column":{"s":35,"e":84}},"dim":["","list.105","listItem.5","paragraph.0","text.3"],"code":" - Alpine.js stores for glass state and darkMode."},{"id":"/root/children/105/children/6","type":"listItem","loc":{"start":34866,"end":34943,"line":{"s":597,"e":597,"code":["7. **New file**: `player/page-file-manager.js` - file manager actions module."]},"column":{"s":0,"e":77}},"dim":["","list.105","listItem.6"],"code":"7. **New file**: `player/page-file-manager.js` - file manager actions module."},{"id":"/root/children/105/children/6/children/0","type":"paragraph","loc":{"start":34869,"end":34943,"line":{"s":597,"e":597,"code":["7. **New file**: `player/page-file-manager.js` - file manager actions module."]},"column":{"s":3,"e":77}},"dim":["","list.105","listItem.6","paragraph.0"],"code":"**New file**: `player/page-file-manager.js` - file manager actions module."},{"id":"/root/children/105/children/6/children/0/children/0","type":"strong","loc":{"start":34869,"end":34881,"line":{"s":597,"e":597,"code":["7. **New file**: `player/page-file-manager.js` - file manager actions module."]},"column":{"s":3,"e":15}},"dim":["","list.105","listItem.6","paragraph.0","strong.0"],"code":"**New file**"},{"id":"/root/children/105/children/6/children/0/children/0/children/0","type":"text","loc":{"start":34871,"end":34879,"line":{"s":597,"e":597,"code":["7. **New file**: `player/page-file-manager.js` - file manager actions module."]},"column":{"s":5,"e":13}},"dim":["","list.105","listItem.6","paragraph.0","strong.0","text.0"],"code":"New file"},{"id":"/root/children/105/children/6/children/0/children/1","type":"text","loc":{"start":34881,"end":34883,"line":{"s":597,"e":597,"code":["7. **New file**: `player/page-file-manager.js` - file manager actions module."]},"column":{"s":15,"e":17}},"dim":["","list.105","listItem.6","paragraph.0","text.1"],"code":": "},{"id":"/root/children/105/children/6/children/0/children/2","type":"inlineCode","loc":{"start":34883,"end":34912,"line":{"s":597,"e":597,"code":["7. **New file**: `player/page-file-manager.js` - file manager actions module."]},"column":{"s":17,"e":46}},"dim":["","list.105","listItem.6","paragraph.0","inlineCode.2"],"code":"`player/page-file-manager.js`"},{"id":"/root/children/105/children/6/children/0/children/3","type":"text","loc":{"start":34912,"end":34943,"line":{"s":597,"e":597,"code":["7. **New file**: `player/page-file-manager.js` - file manager actions module."]},"column":{"s":46,"e":77}},"dim":["","list.105","listItem.6","paragraph.0","text.3"],"code":" - file manager actions module."},{"id":"/root/children/105/children/7","type":"listItem","loc":{"start":34944,"end":35034,"line":{"s":598,"e":598,"code":["8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."]},"column":{"s":0,"e":90}},"dim":["","list.105","listItem.7"],"code":"8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."},{"id":"/root/children/105/children/7/children/0","type":"paragraph","loc":{"start":34947,"end":35034,"line":{"s":598,"e":598,"code":["8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."]},"column":{"s":3,"e":90}},"dim":["","list.105","listItem.7","paragraph.0"],"code":"**New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."},{"id":"/root/children/105/children/7/children/0/children/0","type":"strong","loc":{"start":34947,"end":34959,"line":{"s":598,"e":598,"code":["8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."]},"column":{"s":3,"e":15}},"dim":["","list.105","listItem.7","paragraph.0","strong.0"],"code":"**New file**"},{"id":"/root/children/105/children/7/children/0/children/0/children/0","type":"text","loc":{"start":34949,"end":34957,"line":{"s":598,"e":598,"code":["8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."]},"column":{"s":5,"e":13}},"dim":["","list.105","listItem.7","paragraph.0","strong.0","text.0"],"code":"New file"},{"id":"/root/children/105/children/7/children/0/children/1","type":"text","loc":{"start":34959,"end":34961,"line":{"s":598,"e":598,"code":["8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."]},"column":{"s":15,"e":17}},"dim":["","list.105","listItem.7","paragraph.0","text.1"],"code":": "},{"id":"/root/children/105/children/7/children/0/children/2","type":"inlineCode","loc":{"start":34961,"end":34990,"line":{"s":598,"e":598,"code":["8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."]},"column":{"s":17,"e":46}},"dim":["","list.105","listItem.7","paragraph.0","inlineCode.2"],"code":"`player/simple-datatables.js`"},{"id":"/root/children/105/children/7/children/0/children/3","type":"text","loc":{"start":34990,"end":35034,"line":{"s":598,"e":598,"code":["8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines)."]},"column":{"s":46,"e":90}},"dim":["","list.105","listItem.7","paragraph.0","text.3"],"code":" - bundled DataTables library (~6993 lines)."},{"id":"/root/children/105/children/8","type":"listItem","loc":{"start":35035,"end":35149,"line":{"s":599,"e":599,"code":["9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."]},"column":{"s":0,"e":114}},"dim":["","list.105","listItem.8"],"code":"9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."},{"id":"/root/children/105/children/8/children/0","type":"paragraph","loc":{"start":35038,"end":35149,"line":{"s":599,"e":599,"code":["9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."]},"column":{"s":3,"e":114}},"dim":["","list.105","listItem.8","paragraph.0"],"code":"**Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."},{"id":"/root/children/105/children/8/children/0/children/0","type":"strong","loc":{"start":35038,"end":35067,"line":{"s":599,"e":599,"code":["9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."]},"column":{"s":3,"e":32}},"dim":["","list.105","listItem.8","paragraph.0","strong.0"],"code":"**Global window assignments**"},{"id":"/root/children/105/children/8/children/0/children/0/children/0","type":"text","loc":{"start":35040,"end":35065,"line":{"s":599,"e":599,"code":["9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."]},"column":{"s":5,"e":30}},"dim":["","list.105","listItem.8","paragraph.0","strong.0","text.0"],"code":"Global window assignments"},{"id":"/root/children/105/children/8/children/0/children/1","type":"text","loc":{"start":35067,"end":35088,"line":{"s":599,"e":599,"code":["9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."]},"column":{"s":32,"e":53}},"dim":["","list.105","listItem.8","paragraph.0","text.1"],"code":" - many functions on "},{"id":"/root/children/105/children/8/children/0/children/2","type":"inlineCode","loc":{"start":35088,"end":35096,"line":{"s":599,"e":599,"code":["9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."]},"column":{"s":53,"e":61}},"dim":["","list.105","listItem.8","paragraph.0","inlineCode.2"],"code":"`window`"},{"id":"/root/children/105/children/8/children/0/children/3","type":"text","loc":{"start":35096,"end":35149,"line":{"s":599,"e":599,"code":["9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor)."]},"column":{"s":61,"e":114}},"dim":["","list.105","listItem.8","paragraph.0","text.3"],"code":" (webHookMapTile, emitFanout, closeGlass, theEditor)."},{"id":"/root/children/105/children/9","type":"listItem","loc":{"start":35150,"end":35213,"line":{"s":600,"e":600,"code":["10. **One TASK found** in rc.jq.html (empty execState function)"]},"column":{"s":0,"e":63}},"dim":["","list.105","listItem.9"],"code":"10. **One TASK found** in rc.jq.html (empty execState function)"},{"id":"/root/children/105/children/9/children/0","type":"paragraph","loc":{"start":35154,"end":35213,"line":{"s":600,"e":600,"code":["10. **One TASK found** in rc.jq.html (empty execState function)"]},"column":{"s":4,"e":63}},"dim":["","list.105","listItem.9","paragraph.0"],"code":"**One TASK found** in rc.jq.html (empty execState function)"},{"id":"/root/children/105/children/9/children/0/children/0","type":"strong","loc":{"start":35154,"end":35172,"line":{"s":600,"e":600,"code":["10. **One TASK found** in rc.jq.html (empty execState function)"]},"column":{"s":4,"e":22}},"dim":["","list.105","listItem.9","paragraph.0","strong.0"],"code":"**One TASK found**"},{"id":"/root/children/105/children/9/children/0/children/0/children/0","type":"text","loc":{"start":35156,"end":35170,"line":{"s":600,"e":600,"code":["10. **One TASK found** in rc.jq.html (empty execState function)"]},"column":{"s":6,"e":20}},"dim":["","list.105","listItem.9","paragraph.0","strong.0","text.0"],"code":"One TASK found"},{"id":"/root/children/105/children/9/children/0/children/1","type":"text","loc":{"start":35172,"end":35213,"line":{"s":600,"e":600,"code":["10. **One TASK found** in rc.jq.html (empty execState function)"]},"column":{"s":22,"e":63}},"dim":["","list.105","listItem.9","paragraph.0","text.1"],"code":" in rc.jq.html (empty execState function)"},{"id":"/root/children/105/children/10","type":"listItem","loc":{"start":35214,"end":35424,"line":{"s":601,"e":601,"code":["11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."]},"column":{"s":0,"e":210}},"dim":["","list.105","listItem.10"],"code":"11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."},{"id":"/root/children/105/children/10/children/0","type":"paragraph","loc":{"start":35218,"end":35424,"line":{"s":601,"e":601,"code":["11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."]},"column":{"s":4,"e":210}},"dim":["","list.105","listItem.10","paragraph.0"],"code":"**#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."},{"id":"/root/children/105/children/10/children/0/children/0","type":"strong","loc":{"start":35218,"end":35244,"line":{"s":601,"e":601,"code":["11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."]},"column":{"s":4,"e":30}},"dim":["","list.105","listItem.10","paragraph.0","strong.0"],"code":"**#e-main / #e-secondary**"},{"id":"/root/children/105/children/10/children/0/children/0/children/0","type":"text","loc":{"start":35220,"end":35242,"line":{"s":601,"e":601,"code":["11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."]},"column":{"s":6,"e":28}},"dim":["","list.105","listItem.10","paragraph.0","strong.0","text.0"],"code":"#e-main / #e-secondary"},{"id":"/root/children/105/children/10/children/0/children/1","type":"text","loc":{"start":35244,"end":35424,"line":{"s":601,"e":601,"code":["11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."]},"column":{"s":30,"e":210}},"dim":["","list.105","listItem.10","paragraph.0","text.1"],"code":" in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\"."},{"id":"/root/children/105/children/11","type":"listItem","loc":{"start":35425,"end":35631,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":0,"e":206}},"dim":["","list.105","listItem.11"],"code":"12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."},{"id":"/root/children/105/children/11/children/0","type":"paragraph","loc":{"start":35429,"end":35631,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":4,"e":206}},"dim":["","list.105","listItem.11","paragraph.0"],"code":"**Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."},{"id":"/root/children/105/children/11/children/0/children/0","type":"strong","loc":{"start":35429,"end":35458,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":4,"e":33}},"dim":["","list.105","listItem.11","paragraph.0","strong.0"],"code":"**Glass is a toggle overlay**"},{"id":"/root/children/105/children/11/children/0/children/0/children/0","type":"text","loc":{"start":35431,"end":35456,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":6,"e":31}},"dim":["","list.105","listItem.11","paragraph.0","strong.0","text.0"],"code":"Glass is a toggle overlay"},{"id":"/root/children/105/children/11/children/0/children/1","type":"text","loc":{"start":35458,"end":35483,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":33,"e":58}},"dim":["","list.105","listItem.11","paragraph.0","text.1"],"code":", not a separate \"page\". "},{"id":"/root/children/105/children/11/children/0/children/2","type":"inlineCode","loc":{"start":35483,"end":35497,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":58,"e":72}},"dim":["","list.105","listItem.11","paragraph.0","inlineCode.2"],"code":"`closeGlass()`"},{"id":"/root/children/105/children/11/children/0/children/3","type":"text","loc":{"start":35497,"end":35500,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":72,"e":75}},"dim":["","list.105","listItem.11","paragraph.0","text.3"],"code":" / "},{"id":"/root/children/105/children/11/children/0/children/4","type":"inlineCode","loc":{"start":35500,"end":35517,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":75,"e":92}},"dim":["","list.105","listItem.11","paragraph.0","inlineCode.4"],"code":"`goToPrev(STATE)`"},{"id":"/root/children/105/children/11/children/0/children/5","type":"text","loc":{"start":35517,"end":35566,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":92,"e":141}},"dim":["","list.105","listItem.11","paragraph.0","text.5"],"code":" navigates back to the last non-glass layer. The "},{"id":"/root/children/105/children/11/children/0/children/6","type":"inlineCode","loc":{"start":35566,"end":35583,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":141,"e":158}},"dim":["","list.105","listItem.11","paragraph.0","inlineCode.6"],"code":"`@lastGlassState`"},{"id":"/root/children/105/children/11/children/0/children/7","type":"text","loc":{"start":35583,"end":35631,"line":{"s":602,"e":602,"code":["12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot."]},"column":{"s":158,"e":206}},"dim":["","list.105","listItem.11","paragraph.0","text.7"],"code":" flag controls whether glass auto-opens on boot."},{"id":"/root/children/106","type":"thematicBreak","loc":{"start":35633,"end":35636,"line":{"s":604,"e":604,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.106"],"code":"---"},{"id":"/root/children/107","type":"heading","loc":{"start":35638,"end":35665,"line":{"s":606,"e":606,"code":["## Analysis: PROXY vs proxy"]},"column":{"s":0,"e":27}},"dim":["","heading.107"],"code":"## Analysis: PROXY vs proxy","symbName":"heading","symbRange":[35667,70981],"symbRangeL":[606,607],"outerCode":"","outerHtml":""},{"id":"/root/children/107/children/0","type":"text","loc":{"start":35641,"end":35665,"line":{"s":606,"e":606,"code":["## Analysis: PROXY vs proxy"]},"column":{"s":3,"e":27}},"dim":["","heading.107","text.0"],"code":"Analysis: PROXY vs proxy"},{"id":"/root/children/108","type":"heading","loc":{"start":35667,"end":35687,"line":{"s":608,"e":608,"code":["### Intended pattern"]},"column":{"s":0,"e":20}},"dim":["","heading.108"],"code":"### Intended pattern","symbName":"heading","symbRange":[35689,35860],"symbRangeL":[608,612],"outerCode":"\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`","outerHtml":"\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>"},{"id":"/root/children/108/children/0","type":"text","loc":{"start":35671,"end":35687,"line":{"s":608,"e":608,"code":["### Intended pattern"]},"column":{"s":4,"e":20}},"dim":["","heading.108","text.0"],"code":"Intended pattern"},{"id":"/root/children/109","type":"list","loc":{"start":35689,"end":35860,"line":{"s":610,"e":611,"code":["- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag","- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"]},"column":{"s":0,"e":88}},"dim":["","list.109"],"code":"- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`","symbName":"list","symbRange":[35862,37384],"symbRangeL":[610,639],"outerCode":"- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**","outerHtml":"<ul><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>"},{"id":"/root/children/109/children/0","type":"listItem","loc":{"start":35689,"end":35771,"line":{"s":610,"e":610,"code":["- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag"]},"column":{"s":0,"e":82}},"dim":["","list.109","listItem.0"],"code":"- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag"},{"id":"/root/children/109/children/0/children/0","type":"paragraph","loc":{"start":35691,"end":35771,"line":{"s":610,"e":610,"code":["- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag"]},"column":{"s":2,"e":82}},"dim":["","list.109","listItem.0","paragraph.0"],"code":"`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag"},{"id":"/root/children/109/children/0/children/0/children/0","type":"inlineCode","loc":{"start":35691,"end":35698,"line":{"s":610,"e":610,"code":["- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag"]},"column":{"s":2,"e":9}},"dim":["","list.109","listItem.0","paragraph.0","inlineCode.0"],"code":"`PROXY`"},{"id":"/root/children/109/children/0/children/0/children/1","type":"text","loc":{"start":35698,"end":35771,"line":{"s":610,"e":610,"code":["- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag"]},"column":{"s":9,"e":82}},"dim":["","list.109","listItem.0","paragraph.0","text.1"],"code":" (uppercase, line 41) — module-level singleton, the global dependency bag"},{"id":"/root/children/109/children/1","type":"listItem","loc":{"start":35772,"end":35860,"line":{"s":611,"e":611,"code":["- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"]},"column":{"s":0,"e":88}},"dim":["","list.109","listItem.1"],"code":"- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"},{"id":"/root/children/109/children/1/children/0","type":"paragraph","loc":{"start":35774,"end":35860,"line":{"s":611,"e":611,"code":["- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"]},"column":{"s":2,"e":88}},"dim":["","list.109","listItem.1","paragraph.0"],"code":"`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"},{"id":"/root/children/109/children/1/children/0/children/0","type":"inlineCode","loc":{"start":35774,"end":35781,"line":{"s":611,"e":611,"code":["- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"]},"column":{"s":2,"e":9}},"dim":["","list.109","listItem.1","paragraph.0","inlineCode.0"],"code":"`proxy`"},{"id":"/root/children/109/children/1/children/0/children/1","type":"text","loc":{"start":35781,"end":35853,"line":{"s":611,"e":611,"code":["- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"]},"column":{"s":9,"e":81}},"dim":["","list.109","listItem.1","paragraph.0","text.1"],"code":" (lowercase) — local builder that accumulates props before merging into "},{"id":"/root/children/109/children/1/children/0/children/2","type":"inlineCode","loc":{"start":35853,"end":35860,"line":{"s":611,"e":611,"code":["- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`"]},"column":{"s":81,"e":88}},"dim":["","list.109","listItem.1","paragraph.0","inlineCode.2"],"code":"`PROXY`"},{"id":"/root/children/110","type":"heading","loc":{"start":35862,"end":35887,"line":{"s":613,"e":613,"code":["### What actually happens"]},"column":{"s":0,"e":25}},"dim":["","heading.110"],"code":"### What actually happens","symbName":"heading","symbRange":[35889,36289],"symbRangeL":[613,620],"outerCode":"\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.","outerHtml":"\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>"},{"id":"/root/children/110/children/0","type":"text","loc":{"start":35866,"end":35887,"line":{"s":613,"e":613,"code":["### What actually happens"]},"column":{"s":4,"e":25}},"dim":["","heading.110","text.0"],"code":"What actually happens"},{"id":"/root/children/111","type":"paragraph","loc":{"start":35889,"end":35987,"line":{"s":615,"e":615,"code":["The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader."]},"column":{"s":0,"e":98}},"dim":["","paragraph.111"],"code":"The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader."},{"id":"/root/children/111/children/0","type":"text","loc":{"start":35889,"end":35936,"line":{"s":615,"e":615,"code":["The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader."]},"column":{"s":0,"e":47}},"dim":["","paragraph.111","text.0"],"code":"The builder pattern doesn't exist in the code. "},{"id":"/root/children/111/children/1","type":"inlineCode","loc":{"start":35936,"end":35943,"line":{"s":615,"e":615,"code":["The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader."]},"column":{"s":47,"e":54}},"dim":["","paragraph.111","inlineCode.1"],"code":"`proxy`"},{"id":"/root/children/111/children/2","type":"text","loc":{"start":35943,"end":35987,"line":{"s":615,"e":615,"code":["The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader."]},"column":{"s":54,"e":98}},"dim":["","paragraph.111","text.2"],"code":" is never written to — it's always a reader."},{"id":"/root/children/112","type":"paragraph","loc":{"start":35989,"end":36096,"line":{"s":617,"e":617,"code":["**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton."]},"column":{"s":0,"e":107}},"dim":["","paragraph.112"],"code":"**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton."},{"id":"/root/children/112/children/0","type":"strong","loc":{"start":35989,"end":36004,"line":{"s":617,"e":617,"code":["**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton."]},"column":{"s":0,"e":15}},"dim":["","paragraph.112","strong.0"],"code":"**Merge point**"},{"id":"/root/children/112/children/0/children/0","type":"text","loc":{"start":35991,"end":36002,"line":{"s":617,"e":617,"code":["**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton."]},"column":{"s":2,"e":13}},"dim":["","paragraph.112","strong.0","text.0"],"code":"Merge point"},{"id":"/root/children/112/children/1","type":"text","loc":{"start":36004,"end":36018,"line":{"s":617,"e":617,"code":["**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton."]},"column":{"s":15,"e":29}},"dim":["","paragraph.112","text.1"],"code":" (line 3288): "},{"id":"/root/children/112/children/2","type":"inlineCode","loc":{"start":36018,"end":36075,"line":{"s":617,"e":617,"code":["**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton."]},"column":{"s":29,"e":86}},"dim":["","paragraph.112","inlineCode.2"],"code":"`Object.assign(PROXY, { mapShow, notyf, repoName, ... })`"},{"id":"/root/children/112/children/3","type":"text","loc":{"start":36075,"end":36096,"line":{"s":617,"e":617,"code":["**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton."]},"column":{"s":86,"e":107}},"dim":["","paragraph.112","text.3"],"code":" fills the singleton."},{"id":"/root/children/113","type":"paragraph","loc":{"start":36098,"end":36289,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":0,"e":191}},"dim":["","paragraph.113"],"code":"**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."},{"id":"/root/children/113/children/0","type":"strong","loc":{"start":36098,"end":36111,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":0,"e":13}},"dim":["","paragraph.113","strong.0"],"code":"**Call site**"},{"id":"/root/children/113/children/0/children/0","type":"text","loc":{"start":36100,"end":36109,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":2,"e":11}},"dim":["","paragraph.113","strong.0","text.0"],"code":"Call site"},{"id":"/root/children/113/children/1","type":"text","loc":{"start":36111,"end":36125,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":13,"e":27}},"dim":["","paragraph.113","text.1"],"code":" (line 3505): "},{"id":"/root/children/113/children/2","type":"inlineCode","loc":{"start":36125,"end":36192,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":27,"e":94}},"dim":["","paragraph.113","inlineCode.2"],"code":"`addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })`"},{"id":"/root/children/113/children/3","type":"text","loc":{"start":36192,"end":36245,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":94,"e":147}},"dim":["","paragraph.113","text.3"],"code":" creates a copy of PROXY with extra props, passed as "},{"id":"/root/children/113/children/4","type":"inlineCode","loc":{"start":36245,"end":36252,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":147,"e":154}},"dim":["","paragraph.113","inlineCode.4"],"code":"`proxy`"},{"id":"/root/children/113/children/5","type":"text","loc":{"start":36252,"end":36289,"line":{"s":619,"e":619,"code":["**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it."]},"column":{"s":154,"e":191}},"dim":["","paragraph.113","text.5"],"code":". No function ever writes back to it."},{"id":"/root/children/114","type":"heading","loc":{"start":36291,"end":36328,"line":{"s":621,"e":621,"code":["### Violations — proxy used as reader"]},"column":{"s":0,"e":37}},"dim":["","heading.114"],"code":"### Violations — proxy used as reader","symbName":"heading","symbRange":[36330,37067],"symbRangeL":[621,629],"outerCode":"\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |","outerHtml":"\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>"},{"id":"/root/children/114/children/0","type":"text","loc":{"start":36295,"end":36328,"line":{"s":621,"e":621,"code":["### Violations — proxy used as reader"]},"column":{"s":4,"e":37}},"dim":["","heading.114","text.0"],"code":"Violations — proxy used as reader"},{"id":"/root/children/115","type":"paragraph","loc":{"start":36330,"end":37067,"line":{"s":623,"e":628,"code":["| Function                                 | Line | Reads from `proxy`                                         | Writes? |","| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |","| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |","| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |","| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |","| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |"]},"column":{"s":0,"e":122}},"dim":["","paragraph.115"],"code":"| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |"},{"id":"/root/children/115/children/0","type":"text","loc":{"start":36330,"end":36393,"line":{"s":623,"e":623,"code":["| Function                                 | Line | Reads from `proxy`                                         | Writes? |"]},"column":{"s":0,"e":63}},"dim":["","paragraph.115","text.0"],"code":"| Function                                 | Line | Reads from "},{"id":"/root/children/115/children/1","type":"inlineCode","loc":{"start":36393,"end":36400,"line":{"s":623,"e":623,"code":["| Function                                 | Line | Reads from `proxy`                                         | Writes? |"]},"column":{"s":63,"e":70}},"dim":["","paragraph.115","inlineCode.1"],"code":"`proxy`"},{"id":"/root/children/115/children/2","type":"text","loc":{"start":36400,"end":36578,"line":{"s":623,"e":625,"code":["| Function                                 | Line | Reads from `proxy`                                         | Writes? |","| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |","| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |"]},"column":{"s":70,"e":2}},"dim":["","paragraph.115","text.2"],"code":"                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| "},{"id":"/root/children/115/children/3","type":"inlineCode","loc":{"start":36578,"end":36604,"line":{"s":625,"e":625,"code":["| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |"]},"column":{"s":2,"e":28}},"dim":["","paragraph.115","inlineCode.3"],"code":"`reactArmSelection(proxy)`"},{"id":"/root/children/115/children/4","type":"text","loc":{"start":36604,"end":36628,"line":{"s":625,"e":625,"code":["| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |"]},"column":{"s":28,"e":52}},"dim":["","paragraph.115","text.4"],"code":"               | 306  | "},{"id":"/root/children/115/children/5","type":"inlineCode","loc":{"start":36628,"end":36635,"line":{"s":625,"e":625,"code":["| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |"]},"column":{"s":52,"e":59}},"dim":["","paragraph.115","inlineCode.5"],"code":"`notyf`"},{"id":"/root/children/115/children/6","type":"text","loc":{"start":36635,"end":36701,"line":{"s":625,"e":626,"code":["| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |","| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":59,"e":2}},"dim":["","paragraph.115","text.6"],"code":"                                                    | No      |\n| "},{"id":"/root/children/115/children/7","type":"inlineCode","loc":{"start":36701,"end":36730,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":2,"e":31}},"dim":["","paragraph.115","inlineCode.7"],"code":"`addOneTimeTweaksTodo(proxy)`"},{"id":"/root/children/115/children/8","type":"text","loc":{"start":36730,"end":36751,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":31,"e":52}},"dim":["","paragraph.115","text.8"],"code":"            | 333  | "},{"id":"/root/children/115/children/9","type":"inlineCode","loc":{"start":36751,"end":36757,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":52,"e":58}},"dim":["","paragraph.115","inlineCode.9"],"code":"`ssss`"},{"id":"/root/children/115/children/10","type":"text","loc":{"start":36757,"end":36759,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":58,"e":60}},"dim":["","paragraph.115","text.10"],"code":", "},{"id":"/root/children/115/children/11","type":"inlineCode","loc":{"start":36759,"end":36768,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":60,"e":69}},"dim":["","paragraph.115","inlineCode.11"],"code":"`baseUrl`"},{"id":"/root/children/115/children/12","type":"text","loc":{"start":36768,"end":36770,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":69,"e":71}},"dim":["","paragraph.115","text.12"],"code":", "},{"id":"/root/children/115/children/13","type":"inlineCode","loc":{"start":36770,"end":36779,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":71,"e":80}},"dim":["","paragraph.115","inlineCode.13"],"code":"`crudURL`"},{"id":"/root/children/115/children/14","type":"text","loc":{"start":36779,"end":36781,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":80,"e":82}},"dim":["","paragraph.115","text.14"],"code":", "},{"id":"/root/children/115/children/15","type":"inlineCode","loc":{"start":36781,"end":36800,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":82,"e":101}},"dim":["","paragraph.115","inlineCode.15"],"code":"`emitFanoutMessage`"},{"id":"/root/children/115/children/16","type":"text","loc":{"start":36800,"end":36802,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":101,"e":103}},"dim":["","paragraph.115","text.16"],"code":", "},{"id":"/root/children/115/children/17","type":"inlineCode","loc":{"start":36802,"end":36809,"line":{"s":626,"e":626,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |"]},"column":{"s":103,"e":110}},"dim":["","paragraph.115","inlineCode.17"],"code":"`notyf`"},{"id":"/root/children/115/children/18","type":"text","loc":{"start":36809,"end":36824,"line":{"s":626,"e":627,"code":["| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |","| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |"]},"column":{"s":110,"e":2}},"dim":["","paragraph.115","text.18"],"code":" | No      |\n| "},{"id":"/root/children/115/children/19","type":"inlineCode","loc":{"start":36824,"end":36864,"line":{"s":627,"e":627,"code":["| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |"]},"column":{"s":2,"e":42}},"dim":["","paragraph.115","inlineCode.19"],"code":"`initGlassAutocompleteInput(..., proxy)`"},{"id":"/root/children/115/children/20","type":"text","loc":{"start":36864,"end":36874,"line":{"s":627,"e":627,"code":["| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |"]},"column":{"s":42,"e":52}},"dim":["","paragraph.115","text.20"],"code":" | 570  | "},{"id":"/root/children/115/children/21","type":"inlineCode","loc":{"start":36874,"end":36881,"line":{"s":627,"e":627,"code":["| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |"]},"column":{"s":52,"e":59}},"dim":["","paragraph.115","inlineCode.21"],"code":"`notyf`"},{"id":"/root/children/115/children/22","type":"text","loc":{"start":36881,"end":36947,"line":{"s":627,"e":628,"code":["| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |","| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |"]},"column":{"s":59,"e":2}},"dim":["","paragraph.115","text.22"],"code":"                                                    | No      |\n| "},{"id":"/root/children/115/children/23","type":"inlineCode","loc":{"start":36947,"end":36974,"line":{"s":628,"e":628,"code":["| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |"]},"column":{"s":2,"e":29}},"dim":["","paragraph.115","inlineCode.23"],"code":"`initGlassMenu(..., proxy)`"},{"id":"/root/children/115/children/24","type":"text","loc":{"start":36974,"end":37014,"line":{"s":628,"e":628,"code":["| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |"]},"column":{"s":29,"e":69}},"dim":["","paragraph.115","text.24"],"code":"              | 846  | nothing — passes "},{"id":"/root/children/115/children/25","type":"inlineCode","loc":{"start":37014,"end":37021,"line":{"s":628,"e":628,"code":["| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |"]},"column":{"s":69,"e":76}},"dim":["","paragraph.115","inlineCode.25"],"code":"`proxy`"},{"id":"/root/children/115/children/26","type":"text","loc":{"start":37021,"end":37067,"line":{"s":628,"e":628,"code":["| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |"]},"column":{"s":76,"e":122}},"dim":["","paragraph.115","text.26"],"code":" down                              | No      |"},{"id":"/root/children/116","type":"heading","loc":{"start":37069,"end":37084,"line":{"s":630,"e":630,"code":["### Consequence"]},"column":{"s":0,"e":15}},"dim":["","heading.116"],"code":"### Consequence","symbName":"heading","symbRange":[37086,37258],"symbRangeL":[630,633],"outerCode":"\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.","outerHtml":"\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>"},{"id":"/root/children/116/children/0","type":"text","loc":{"start":37073,"end":37084,"line":{"s":630,"e":630,"code":["### Consequence"]},"column":{"s":4,"e":15}},"dim":["","heading.116","text.0"],"code":"Consequence"},{"id":"/root/children/117","type":"paragraph","loc":{"start":37086,"end":37258,"line":{"s":632,"e":632,"code":["`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads."]},"column":{"s":0,"e":172}},"dim":["","paragraph.117"],"code":"`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads."},{"id":"/root/children/117/children/0","type":"inlineCode","loc":{"start":37086,"end":37093,"line":{"s":632,"e":632,"code":["`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads."]},"column":{"s":0,"e":7}},"dim":["","paragraph.117","inlineCode.0"],"code":"`proxy`"},{"id":"/root/children/117/children/1","type":"text","loc":{"start":37093,"end":37258,"line":{"s":632,"e":632,"code":["`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads."]},"column":{"s":7,"e":172}},"dim":["","paragraph.117","text.1"],"code":" is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads."},{"id":"/root/children/118","type":"heading","loc":{"start":37260,"end":37275,"line":{"s":634,"e":634,"code":["### Fix applied"]},"column":{"s":0,"e":15}},"dim":["","heading.118"],"code":"### Fix applied","symbName":"heading","symbRange":[37277,38468],"symbRangeL":[634,655],"outerCode":"\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---","outerHtml":"\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>"},{"id":"/root/children/118/children/0","type":"text","loc":{"start":37264,"end":37275,"line":{"s":634,"e":634,"code":["### Fix applied"]},"column":{"s":4,"e":15}},"dim":["","heading.118","text.0"],"code":"Fix applied"},{"id":"/root/children/119","type":"paragraph","loc":{"start":37277,"end":37357,"line":{"s":636,"e":636,"code":["Enforced the pattern: `PROXY` is the singleton, functions read from it directly."]},"column":{"s":0,"e":80}},"dim":["","paragraph.119"],"code":"Enforced the pattern: `PROXY` is the singleton, functions read from it directly."},{"id":"/root/children/119/children/0","type":"text","loc":{"start":37277,"end":37299,"line":{"s":636,"e":636,"code":["Enforced the pattern: `PROXY` is the singleton, functions read from it directly."]},"column":{"s":0,"e":22}},"dim":["","paragraph.119","text.0"],"code":"Enforced the pattern: "},{"id":"/root/children/119/children/1","type":"inlineCode","loc":{"start":37299,"end":37306,"line":{"s":636,"e":636,"code":["Enforced the pattern: `PROXY` is the singleton, functions read from it directly."]},"column":{"s":22,"e":29}},"dim":["","paragraph.119","inlineCode.1"],"code":"`PROXY`"},{"id":"/root/children/119/children/2","type":"text","loc":{"start":37306,"end":37357,"line":{"s":636,"e":636,"code":["Enforced the pattern: `PROXY` is the singleton, functions read from it directly."]},"column":{"s":29,"e":80}},"dim":["","paragraph.119","text.2"],"code":" is the singleton, functions read from it directly."},{"id":"/root/children/120","type":"paragraph","loc":{"start":37359,"end":37384,"line":{"s":638,"e":638,"code":["**Changes in player.js:**"]},"column":{"s":0,"e":25}},"dim":["","paragraph.120"],"code":"**Changes in player.js:**"},{"id":"/root/children/120/children/0","type":"strong","loc":{"start":37359,"end":37384,"line":{"s":638,"e":638,"code":["**Changes in player.js:**"]},"column":{"s":0,"e":25}},"dim":["","paragraph.120","strong.0"],"code":"**Changes in player.js:**"},{"id":"/root/children/120/children/0/children/0","type":"text","loc":{"start":37361,"end":37382,"line":{"s":638,"e":638,"code":["**Changes in player.js:**"]},"column":{"s":2,"e":23}},"dim":["","paragraph.120","strong.0","text.0"],"code":"Changes in player.js:"},{"id":"/root/children/121","type":"list","loc":{"start":37386,"end":37962,"line":{"s":640,"e":645,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site","- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`","- All internal functions now read from module-level `PROXY`","- Removed unused `PROXY` shadow param from `savePaper()`","- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)","- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":0,"e":121}},"dim":["","list.121"],"code":"- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`","symbName":"list","symbRange":[37964,37996],"symbRangeL":[640,648],"outerCode":"- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**","outerHtml":"<ul><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>"},{"id":"/root/children/121/children/0","type":"listItem","loc":{"start":37386,"end":37516,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":0,"e":130}},"dim":["","list.121","listItem.0"],"code":"- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"},{"id":"/root/children/121/children/0/children/0","type":"paragraph","loc":{"start":37388,"end":37516,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":2,"e":130}},"dim":["","list.121","listItem.0","paragraph.0"],"code":"Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"},{"id":"/root/children/121/children/0/children/0/children/0","type":"text","loc":{"start":37388,"end":37394,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":2,"e":8}},"dim":["","list.121","listItem.0","paragraph.0","text.0"],"code":"Added "},{"id":"/root/children/121/children/0/children/0/children/1","type":"inlineCode","loc":{"start":37394,"end":37400,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":8,"e":14}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.1"],"code":"`ssss`"},{"id":"/root/children/121/children/0/children/0/children/2","type":"text","loc":{"start":37400,"end":37402,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":14,"e":16}},"dim":["","list.121","listItem.0","paragraph.0","text.2"],"code":", "},{"id":"/root/children/121/children/0/children/0/children/3","type":"inlineCode","loc":{"start":37402,"end":37409,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":16,"e":23}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.3"],"code":"`STATE`"},{"id":"/root/children/121/children/0/children/0/children/4","type":"text","loc":{"start":37409,"end":37411,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":23,"e":25}},"dim":["","list.121","listItem.0","paragraph.0","text.4"],"code":", "},{"id":"/root/children/121/children/0/children/0/children/5","type":"inlineCode","loc":{"start":37411,"end":37420,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":25,"e":34}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.5"],"code":"`baseUrl`"},{"id":"/root/children/121/children/0/children/0/children/6","type":"text","loc":{"start":37420,"end":37422,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":34,"e":36}},"dim":["","list.121","listItem.0","paragraph.0","text.6"],"code":", "},{"id":"/root/children/121/children/0/children/0/children/7","type":"inlineCode","loc":{"start":37422,"end":37435,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":36,"e":49}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.7"],"code":"`repoUrlBase`"},{"id":"/root/children/121/children/0/children/0/children/8","type":"text","loc":{"start":37435,"end":37437,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":49,"e":51}},"dim":["","list.121","listItem.0","paragraph.0","text.8"],"code":", "},{"id":"/root/children/121/children/0/children/0/children/9","type":"inlineCode","loc":{"start":37437,"end":37446,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":51,"e":60}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.9"],"code":"`crudURL`"},{"id":"/root/children/121/children/0/children/0/children/10","type":"text","loc":{"start":37446,"end":37448,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":60,"e":62}},"dim":["","list.121","listItem.0","paragraph.0","text.10"],"code":", "},{"id":"/root/children/121/children/0/children/0/children/11","type":"inlineCode","loc":{"start":37448,"end":37464,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":62,"e":78}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.11"],"code":"`addResultItems`"},{"id":"/root/children/121/children/0/children/0/children/12","type":"text","loc":{"start":37464,"end":37468,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":78,"e":82}},"dim":["","list.121","listItem.0","paragraph.0","text.12"],"code":" to "},{"id":"/root/children/121/children/0/children/0/children/13","type":"inlineCode","loc":{"start":37468,"end":37495,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":82,"e":109}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.13"],"code":"`Object.assign(PROXY, ...)`"},{"id":"/root/children/121/children/0/children/0/children/14","type":"text","loc":{"start":37495,"end":37516,"line":{"s":640,"e":640,"code":["- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site"]},"column":{"s":109,"e":130}},"dim":["","list.121","listItem.0","paragraph.0","text.14"],"code":" at the assembly site"},{"id":"/root/children/121/children/1","type":"listItem","loc":{"start":37517,"end":37649,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":0,"e":132}},"dim":["","list.121","listItem.1"],"code":"- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"},{"id":"/root/children/121/children/1/children/0","type":"paragraph","loc":{"start":37519,"end":37649,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":2,"e":132}},"dim":["","list.121","listItem.1","paragraph.0"],"code":"Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"},{"id":"/root/children/121/children/1/children/0/children/0","type":"text","loc":{"start":37519,"end":37527,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":2,"e":10}},"dim":["","list.121","listItem.1","paragraph.0","text.0"],"code":"Removed "},{"id":"/root/children/121/children/1/children/0/children/1","type":"inlineCode","loc":{"start":37527,"end":37534,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":10,"e":17}},"dim":["","list.121","listItem.1","paragraph.0","inlineCode.1"],"code":"`proxy`"},{"id":"/root/children/121/children/1/children/0/children/2","type":"text","loc":{"start":37534,"end":37551,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":17,"e":34}},"dim":["","list.121","listItem.1","paragraph.0","text.2"],"code":" parameter from: "},{"id":"/root/children/121/children/1/children/0/children/3","type":"inlineCode","loc":{"start":37551,"end":37572,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":34,"e":55}},"dim":["","list.121","listItem.1","paragraph.0","inlineCode.3"],"code":"`reactArmSelection()`"},{"id":"/root/children/121/children/1/children/0/children/4","type":"text","loc":{"start":37572,"end":37574,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":55,"e":57}},"dim":["","list.121","listItem.1","paragraph.0","text.4"],"code":", "},{"id":"/root/children/121/children/1/children/0/children/5","type":"inlineCode","loc":{"start":37574,"end":37598,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":57,"e":81}},"dim":["","list.121","listItem.1","paragraph.0","inlineCode.5"],"code":"`addOneTimeTweaksTodo()`"},{"id":"/root/children/121/children/1/children/0/children/6","type":"text","loc":{"start":37598,"end":37600,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":81,"e":83}},"dim":["","list.121","listItem.1","paragraph.0","text.6"],"code":", "},{"id":"/root/children/121/children/1/children/0/children/7","type":"inlineCode","loc":{"start":37600,"end":37630,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":83,"e":113}},"dim":["","list.121","listItem.1","paragraph.0","inlineCode.7"],"code":"`initGlassAutocompleteInput()`"},{"id":"/root/children/121/children/1/children/0/children/8","type":"text","loc":{"start":37630,"end":37632,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":113,"e":115}},"dim":["","list.121","listItem.1","paragraph.0","text.8"],"code":", "},{"id":"/root/children/121/children/1/children/0/children/9","type":"inlineCode","loc":{"start":37632,"end":37649,"line":{"s":641,"e":641,"code":["- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`"]},"column":{"s":115,"e":132}},"dim":["","list.121","listItem.1","paragraph.0","inlineCode.9"],"code":"`initGlassMenu()`"},{"id":"/root/children/121/children/2","type":"listItem","loc":{"start":37650,"end":37709,"line":{"s":642,"e":642,"code":["- All internal functions now read from module-level `PROXY`"]},"column":{"s":0,"e":59}},"dim":["","list.121","listItem.2"],"code":"- All internal functions now read from module-level `PROXY`"},{"id":"/root/children/121/children/2/children/0","type":"paragraph","loc":{"start":37652,"end":37709,"line":{"s":642,"e":642,"code":["- All internal functions now read from module-level `PROXY`"]},"column":{"s":2,"e":59}},"dim":["","list.121","listItem.2","paragraph.0"],"code":"All internal functions now read from module-level `PROXY`"},{"id":"/root/children/121/children/2/children/0/children/0","type":"text","loc":{"start":37652,"end":37702,"line":{"s":642,"e":642,"code":["- All internal functions now read from module-level `PROXY`"]},"column":{"s":2,"e":52}},"dim":["","list.121","listItem.2","paragraph.0","text.0"],"code":"All internal functions now read from module-level "},{"id":"/root/children/121/children/2/children/0/children/1","type":"inlineCode","loc":{"start":37702,"end":37709,"line":{"s":642,"e":642,"code":["- All internal functions now read from module-level `PROXY`"]},"column":{"s":52,"e":59}},"dim":["","list.121","listItem.2","paragraph.0","inlineCode.1"],"code":"`PROXY`"},{"id":"/root/children/121/children/3","type":"listItem","loc":{"start":37710,"end":37766,"line":{"s":643,"e":643,"code":["- Removed unused `PROXY` shadow param from `savePaper()`"]},"column":{"s":0,"e":56}},"dim":["","list.121","listItem.3"],"code":"- Removed unused `PROXY` shadow param from `savePaper()`"},{"id":"/root/children/121/children/3/children/0","type":"paragraph","loc":{"start":37712,"end":37766,"line":{"s":643,"e":643,"code":["- Removed unused `PROXY` shadow param from `savePaper()`"]},"column":{"s":2,"e":56}},"dim":["","list.121","listItem.3","paragraph.0"],"code":"Removed unused `PROXY` shadow param from `savePaper()`"},{"id":"/root/children/121/children/3/children/0/children/0","type":"text","loc":{"start":37712,"end":37727,"line":{"s":643,"e":643,"code":["- Removed unused `PROXY` shadow param from `savePaper()`"]},"column":{"s":2,"e":17}},"dim":["","list.121","listItem.3","paragraph.0","text.0"],"code":"Removed unused "},{"id":"/root/children/121/children/3/children/0/children/1","type":"inlineCode","loc":{"start":37727,"end":37734,"line":{"s":643,"e":643,"code":["- Removed unused `PROXY` shadow param from `savePaper()`"]},"column":{"s":17,"e":24}},"dim":["","list.121","listItem.3","paragraph.0","inlineCode.1"],"code":"`PROXY`"},{"id":"/root/children/121/children/3/children/0/children/2","type":"text","loc":{"start":37734,"end":37753,"line":{"s":643,"e":643,"code":["- Removed unused `PROXY` shadow param from `savePaper()`"]},"column":{"s":24,"e":43}},"dim":["","list.121","listItem.3","paragraph.0","text.2"],"code":" shadow param from "},{"id":"/root/children/121/children/3/children/0/children/3","type":"inlineCode","loc":{"start":37753,"end":37766,"line":{"s":643,"e":643,"code":["- Removed unused `PROXY` shadow param from `savePaper()`"]},"column":{"s":43,"e":56}},"dim":["","list.121","listItem.3","paragraph.0","inlineCode.3"],"code":"`savePaper()`"},{"id":"/root/children/121/children/4","type":"listItem","loc":{"start":37767,"end":37840,"line":{"s":644,"e":644,"code":["- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"]},"column":{"s":0,"e":73}},"dim":["","list.121","listItem.4"],"code":"- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"},{"id":"/root/children/121/children/4/children/0","type":"paragraph","loc":{"start":37769,"end":37840,"line":{"s":644,"e":644,"code":["- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"]},"column":{"s":2,"e":73}},"dim":["","list.121","listItem.4","paragraph.0"],"code":"Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"},{"id":"/root/children/121/children/4/children/0/children/0","type":"text","loc":{"start":37769,"end":37777,"line":{"s":644,"e":644,"code":["- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"]},"column":{"s":2,"e":10}},"dim":["","list.121","listItem.4","paragraph.0","text.0"],"code":"Removed "},{"id":"/root/children/121/children/4/children/0/children/1","type":"inlineCode","loc":{"start":37777,"end":37784,"line":{"s":644,"e":644,"code":["- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"]},"column":{"s":10,"e":17}},"dim":["","list.121","listItem.4","paragraph.0","inlineCode.1"],"code":"`PROXY`"},{"id":"/root/children/121/children/4/children/0/children/2","type":"text","loc":{"start":37784,"end":37794,"line":{"s":644,"e":644,"code":["- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"]},"column":{"s":17,"e":27}},"dim":["","list.121","listItem.4","paragraph.0","text.2"],"code":" arg from "},{"id":"/root/children/121/children/4/children/0/children/3","type":"inlineCode","loc":{"start":37794,"end":37809,"line":{"s":644,"e":644,"code":["- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"]},"column":{"s":27,"e":42}},"dim":["","list.121","listItem.4","paragraph.0","inlineCode.3"],"code":"`detectActor()`"},{"id":"/root/children/121/children/4/children/0/children/4","type":"text","loc":{"start":37809,"end":37840,"line":{"s":644,"e":644,"code":["- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)"]},"column":{"s":42,"e":73}},"dim":["","list.121","listItem.4","paragraph.0","text.4"],"code":" calls (was unused in larry.js)"},{"id":"/root/children/121/children/5","type":"listItem","loc":{"start":37841,"end":37962,"line":{"s":645,"e":645,"code":["- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":0,"e":121}},"dim":["","list.121","listItem.5"],"code":"- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"},{"id":"/root/children/121/children/5/children/0","type":"paragraph","loc":{"start":37843,"end":37962,"line":{"s":645,"e":645,"code":["- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":2,"e":121}},"dim":["","list.121","listItem.5","paragraph.0"],"code":"`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"},{"id":"/root/children/121/children/5/children/0/children/0","type":"inlineCode","loc":{"start":37843,"end":37867,"line":{"s":645,"e":645,"code":["- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":2,"e":26}},"dim":["","list.121","listItem.5","paragraph.0","inlineCode.0"],"code":"`addOneTimeTweaksTodo()`"},{"id":"/root/children/121/children/5/children/0/children/1","type":"text","loc":{"start":37867,"end":37889,"line":{"s":645,"e":645,"code":["- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":26,"e":48}},"dim":["","list.121","listItem.5","paragraph.0","text.1"],"code":" call simplified from "},{"id":"/root/children/121/children/5/children/0/children/2","type":"inlineCode","loc":{"start":37889,"end":37934,"line":{"s":645,"e":645,"code":["- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":48,"e":93}},"dim":["","list.121","listItem.5","paragraph.0","inlineCode.2"],"code":"`addOneTimeTweaksTodo({...PROXY, ssss, ...})`"},{"id":"/root/children/121/children/5/children/0/children/3","type":"text","loc":{"start":37934,"end":37938,"line":{"s":645,"e":645,"code":["- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":93,"e":97}},"dim":["","list.121","listItem.5","paragraph.0","text.3"],"code":" to "},{"id":"/root/children/121/children/5/children/0/children/4","type":"inlineCode","loc":{"start":37938,"end":37962,"line":{"s":645,"e":645,"code":["- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`"]},"column":{"s":97,"e":121}},"dim":["","list.121","listItem.5","paragraph.0","inlineCode.4"],"code":"`addOneTimeTweaksTodo()`"},{"id":"/root/children/122","type":"paragraph","loc":{"start":37964,"end":37996,"line":{"s":647,"e":647,"code":["**Changes in external modules:**"]},"column":{"s":0,"e":32}},"dim":["","paragraph.122"],"code":"**Changes in external modules:**"},{"id":"/root/children/122/children/0","type":"strong","loc":{"start":37964,"end":37996,"line":{"s":647,"e":647,"code":["**Changes in external modules:**"]},"column":{"s":0,"e":32}},"dim":["","paragraph.122","strong.0"],"code":"**Changes in external modules:**"},{"id":"/root/children/122/children/0/children/0","type":"text","loc":{"start":37966,"end":37994,"line":{"s":647,"e":647,"code":["**Changes in external modules:**"]},"column":{"s":2,"e":30}},"dim":["","paragraph.122","strong.0","text.0"],"code":"Changes in external modules:"},{"id":"/root/children/123","type":"list","loc":{"start":37998,"end":38463,"line":{"s":649,"e":652,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`","- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`","- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call","- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":0,"e":169}},"dim":["","list.123"],"code":"- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const","symbName":"list","symbRange":[38465,43082],"symbRangeL":[649,801],"outerCode":"- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done","outerHtml":"<ul><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>"},{"id":"/root/children/123/children/0","type":"listItem","loc":{"start":37998,"end":38061,"line":{"s":649,"e":649,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`"]},"column":{"s":0,"e":63}},"dim":["","list.123","listItem.0"],"code":"- `larry.js`: removed unused `PROXY` param from `detectActor()`"},{"id":"/root/children/123/children/0/children/0","type":"paragraph","loc":{"start":38000,"end":38061,"line":{"s":649,"e":649,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`"]},"column":{"s":2,"e":63}},"dim":["","list.123","listItem.0","paragraph.0"],"code":"`larry.js`: removed unused `PROXY` param from `detectActor()`"},{"id":"/root/children/123/children/0/children/0/children/0","type":"inlineCode","loc":{"start":38000,"end":38010,"line":{"s":649,"e":649,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`"]},"column":{"s":2,"e":12}},"dim":["","list.123","listItem.0","paragraph.0","inlineCode.0"],"code":"`larry.js`"},{"id":"/root/children/123/children/0/children/0/children/1","type":"text","loc":{"start":38010,"end":38027,"line":{"s":649,"e":649,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`"]},"column":{"s":12,"e":29}},"dim":["","list.123","listItem.0","paragraph.0","text.1"],"code":": removed unused "},{"id":"/root/children/123/children/0/children/0/children/2","type":"inlineCode","loc":{"start":38027,"end":38034,"line":{"s":649,"e":649,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`"]},"column":{"s":29,"e":36}},"dim":["","list.123","listItem.0","paragraph.0","inlineCode.2"],"code":"`PROXY`"},{"id":"/root/children/123/children/0/children/0/children/3","type":"text","loc":{"start":38034,"end":38046,"line":{"s":649,"e":649,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`"]},"column":{"s":36,"e":48}},"dim":["","list.123","listItem.0","paragraph.0","text.3"],"code":" param from "},{"id":"/root/children/123/children/0/children/0/children/4","type":"inlineCode","loc":{"start":38046,"end":38061,"line":{"s":649,"e":649,"code":["- `larry.js`: removed unused `PROXY` param from `detectActor()`"]},"column":{"s":48,"e":63}},"dim":["","list.123","listItem.0","paragraph.0","inlineCode.4"],"code":"`detectActor()`"},{"id":"/root/children/123/children/1","type":"listItem","loc":{"start":38062,"end":38143,"line":{"s":650,"e":650,"code":["- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"]},"column":{"s":0,"e":81}},"dim":["","list.123","listItem.1"],"code":"- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"},{"id":"/root/children/123/children/1/children/0","type":"paragraph","loc":{"start":38064,"end":38143,"line":{"s":650,"e":650,"code":["- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"]},"column":{"s":2,"e":81}},"dim":["","list.123","listItem.1","paragraph.0"],"code":"`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"},{"id":"/root/children/123/children/1/children/0/children/0","type":"inlineCode","loc":{"start":38064,"end":38087,"line":{"s":650,"e":650,"code":["- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"]},"column":{"s":2,"e":25}},"dim":["","list.123","listItem.1","paragraph.0","inlineCode.0"],"code":"`search/search-repo.js`"},{"id":"/root/children/123/children/1/children/0/children/1","type":"text","loc":{"start":38087,"end":38104,"line":{"s":650,"e":650,"code":["- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"]},"column":{"s":25,"e":42}},"dim":["","list.123","listItem.1","paragraph.0","text.1"],"code":": removed unused "},{"id":"/root/children/123/children/1/children/0/children/2","type":"inlineCode","loc":{"start":38104,"end":38111,"line":{"s":650,"e":650,"code":["- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"]},"column":{"s":42,"e":49}},"dim":["","list.123","listItem.1","paragraph.0","inlineCode.2"],"code":"`proxy`"},{"id":"/root/children/123/children/1/children/0/children/3","type":"text","loc":{"start":38111,"end":38123,"line":{"s":650,"e":650,"code":["- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"]},"column":{"s":49,"e":61}},"dim":["","list.123","listItem.1","paragraph.0","text.3"],"code":" param from "},{"id":"/root/children/123/children/1/children/0/children/4","type":"inlineCode","loc":{"start":38123,"end":38143,"line":{"s":650,"e":650,"code":["- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`"]},"column":{"s":61,"e":81}},"dim":["","list.123","listItem.1","paragraph.0","inlineCode.4"],"code":"`searchInRepoJson()`"},{"id":"/root/children/123/children/2","type":"listItem","loc":{"start":38144,"end":38293,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":0,"e":149}},"dim":["","list.123","listItem.2"],"code":"- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"},{"id":"/root/children/123/children/2/children/0","type":"paragraph","loc":{"start":38146,"end":38293,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":2,"e":149}},"dim":["","list.123","listItem.2","paragraph.0"],"code":"`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"},{"id":"/root/children/123/children/2/children/0/children/0","type":"inlineCode","loc":{"start":38146,"end":38163,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":2,"e":19}},"dim":["","list.123","listItem.2","paragraph.0","inlineCode.0"],"code":"`glass-search.js`"},{"id":"/root/children/123/children/2/children/0/children/1","type":"text","loc":{"start":38163,"end":38173,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":19,"e":29}},"dim":["","list.123","listItem.2","paragraph.0","text.1"],"code":": removed "},{"id":"/root/children/123/children/2/children/0/children/2","type":"inlineCode","loc":{"start":38173,"end":38212,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":29,"e":68}},"dim":["","list.123","listItem.2","paragraph.0","inlineCode.2"],"code":"`proxy.addResultItems = addResultItems`"},{"id":"/root/children/123/children/2/children/0/children/3","type":"text","loc":{"start":38212,"end":38255,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":68,"e":111}},"dim":["","list.123","listItem.2","paragraph.0","text.3"],"code":" write (now on PROXY at assembly). Removed "},{"id":"/root/children/123/children/2/children/0/children/4","type":"inlineCode","loc":{"start":38255,"end":38262,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":111,"e":118}},"dim":["","list.123","listItem.2","paragraph.0","inlineCode.4"],"code":"`proxy`"},{"id":"/root/children/123/children/2/children/0/children/5","type":"text","loc":{"start":38262,"end":38268,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":118,"e":124}},"dim":["","list.123","listItem.2","paragraph.0","text.5"],"code":" from "},{"id":"/root/children/123/children/2/children/0/children/6","type":"inlineCode","loc":{"start":38268,"end":38288,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":124,"e":144}},"dim":["","list.123","listItem.2","paragraph.0","inlineCode.6"],"code":"`searchInRepoJson()`"},{"id":"/root/children/123/children/2/children/0/children/7","type":"text","loc":{"start":38288,"end":38293,"line":{"s":651,"e":651,"code":["- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call"]},"column":{"s":144,"e":149}},"dim":["","list.123","listItem.2","paragraph.0","text.7"],"code":" call"},{"id":"/root/children/123/children/3","type":"listItem","loc":{"start":38294,"end":38463,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":0,"e":169}},"dim":["","list.123","listItem.3"],"code":"- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"},{"id":"/root/children/123/children/3/children/0","type":"paragraph","loc":{"start":38296,"end":38463,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":2,"e":169}},"dim":["","list.123","listItem.3","paragraph.0"],"code":"`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"},{"id":"/root/children/123/children/3/children/0/children/0","type":"inlineCode","loc":{"start":38296,"end":38313,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":2,"e":19}},"dim":["","list.123","listItem.3","paragraph.0","inlineCode.0"],"code":"`glass-search.js`"},{"id":"/root/children/123/children/3/children/0/children/1","type":"text","loc":{"start":38313,"end":38316,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":19,"e":22}},"dim":["","list.123","listItem.3","paragraph.0","text.1"],"code":" + "},{"id":"/root/children/123/children/3/children/0/children/2","type":"inlineCode","loc":{"start":38316,"end":38344,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":22,"e":50}},"dim":["","list.123","listItem.3","paragraph.0","inlineCode.2"],"code":"`search/search-fragments.js`"},{"id":"/root/children/123/children/3/children/0/children/3","type":"text","loc":{"start":38344,"end":38360,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":50,"e":66}},"dim":["","list.123","listItem.3","paragraph.0","text.3"],"code":": still receive "},{"id":"/root/children/123/children/3/children/0/children/4","type":"inlineCode","loc":{"start":38360,"end":38367,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":66,"e":73}},"dim":["","list.123","listItem.3","paragraph.0","inlineCode.4"],"code":"`proxy`"},{"id":"/root/children/123/children/3/children/0/children/5","type":"text","loc":{"start":38367,"end":38463,"line":{"s":652,"e":652,"code":["- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const"]},"column":{"s":73,"e":169}},"dim":["","list.123","listItem.3","paragraph.0","text.5"],"code":" (actually PROXY) as param — these are external modules that can't access the module-level const"},{"id":"/root/children/124","type":"thematicBreak","loc":{"start":38465,"end":38468,"line":{"s":654,"e":654,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.124"],"code":"---"},{"id":"/root/children/125","type":"heading","loc":{"start":38470,"end":38505,"line":{"s":656,"e":656,"code":["## Refactoring: boot() + actions.js"]},"column":{"s":0,"e":35}},"dim":["","heading.125"],"code":"## Refactoring: boot() + actions.js","symbName":"heading","symbRange":[38507,70981],"symbRangeL":[656,657],"outerCode":"","outerHtml":""},{"id":"/root/children/125/children/0","type":"text","loc":{"start":38473,"end":38505,"line":{"s":656,"e":656,"code":["## Refactoring: boot() + actions.js"]},"column":{"s":3,"e":35}},"dim":["","heading.125","text.0"],"code":"Refactoring: boot() + actions.js"},{"id":"/root/children/126","type":"heading","loc":{"start":38507,"end":38523,"line":{"s":658,"e":658,"code":["### What changed"]},"column":{"s":0,"e":16}},"dim":["","heading.126"],"code":"### What changed","symbName":"heading","symbRange":[38525,38794],"symbRangeL":[658,661],"outerCode":"\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.","outerHtml":"\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>"},{"id":"/root/children/126/children/0","type":"text","loc":{"start":38511,"end":38523,"line":{"s":658,"e":658,"code":["### What changed"]},"column":{"s":4,"e":16}},"dim":["","heading.126","text.0"],"code":"What changed"},{"id":"/root/children/127","type":"paragraph","loc":{"start":38525,"end":38794,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":0,"e":269}},"dim":["","paragraph.127"],"code":"Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."},{"id":"/root/children/127/children/0","type":"text","loc":{"start":38525,"end":38665,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":0,"e":140}},"dim":["","paragraph.127","text.0"],"code":"Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, "},{"id":"/root/children/127/children/1","type":"inlineCode","loc":{"start":38665,"end":38681,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":140,"e":156}},"dim":["","paragraph.127","inlineCode.1"],"code":"`initRepolero()`"},{"id":"/root/children/127/children/2","type":"text","loc":{"start":38681,"end":38683,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":156,"e":158}},"dim":["","paragraph.127","text.2"],"code":", "},{"id":"/root/children/127/children/3","type":"inlineCode","loc":{"start":38683,"end":38698,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":158,"e":173}},"dim":["","paragraph.127","inlineCode.3"],"code":"`initMqStomp()`"},{"id":"/root/children/127/children/4","type":"text","loc":{"start":38698,"end":38700,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":173,"e":175}},"dim":["","paragraph.127","text.4"],"code":", "},{"id":"/root/children/127/children/5","type":"inlineCode","loc":{"start":38700,"end":38710,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":175,"e":185}},"dim":["","paragraph.127","inlineCode.5"],"code":"`player()`"},{"id":"/root/children/127/children/6","type":"text","loc":{"start":38710,"end":38721,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":185,"e":196}},"dim":["","paragraph.127","text.6"],"code":" call, and "},{"id":"/root/children/127/children/7","type":"inlineCode","loc":{"start":38721,"end":38731,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":196,"e":206}},"dim":["","paragraph.127","inlineCode.7"],"code":"`window.*`"},{"id":"/root/children/127/children/8","type":"text","loc":{"start":38731,"end":38775,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":206,"e":250}},"dim":["","paragraph.127","text.8"],"code":" action definitions. After: each page calls "},{"id":"/root/children/127/children/9","type":"inlineCode","loc":{"start":38775,"end":38793,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":250,"e":268}},"dim":["","paragraph.127","inlineCode.9"],"code":"`boot(ssss, opts)`"},{"id":"/root/children/127/children/10","type":"text","loc":{"start":38793,"end":38794,"line":{"s":660,"e":660,"code":["Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`."]},"column":{"s":268,"e":269}},"dim":["","paragraph.127","text.10"],"code":"."},{"id":"/root/children/128","type":"heading","loc":{"start":38796,"end":38809,"line":{"s":662,"e":662,"code":["### New files"]},"column":{"s":0,"e":13}},"dim":["","heading.128"],"code":"### New files","symbName":"heading","symbRange":[38811,39264],"symbRangeL":[662,680],"outerCode":"\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```","outerHtml":"\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>"},{"id":"/root/children/128/children/0","type":"text","loc":{"start":38800,"end":38809,"line":{"s":662,"e":662,"code":["### New files"]},"column":{"s":4,"e":13}},"dim":["","heading.128","text.0"],"code":"New files"},{"id":"/root/children/129","type":"paragraph","loc":{"start":38811,"end":38869,"line":{"s":664,"e":664,"code":["**actions.js** — shared `window.*` function registrations:"]},"column":{"s":0,"e":58}},"dim":["","paragraph.129"],"code":"**actions.js** — shared `window.*` function registrations:"},{"id":"/root/children/129/children/0","type":"strong","loc":{"start":38811,"end":38825,"line":{"s":664,"e":664,"code":["**actions.js** — shared `window.*` function registrations:"]},"column":{"s":0,"e":14}},"dim":["","paragraph.129","strong.0"],"code":"**actions.js**"},{"id":"/root/children/129/children/0/children/0","type":"text","loc":{"start":38813,"end":38823,"line":{"s":664,"e":664,"code":["**actions.js** — shared `window.*` function registrations:"]},"column":{"s":2,"e":12}},"dim":["","paragraph.129","strong.0","text.0"],"code":"actions.js"},{"id":"/root/children/129/children/1","type":"text","loc":{"start":38825,"end":38835,"line":{"s":664,"e":664,"code":["**actions.js** — shared `window.*` function registrations:"]},"column":{"s":14,"e":24}},"dim":["","paragraph.129","text.1"],"code":" — shared "},{"id":"/root/children/129/children/2","type":"inlineCode","loc":{"start":38835,"end":38845,"line":{"s":664,"e":664,"code":["**actions.js** — shared `window.*` function registrations:"]},"column":{"s":24,"e":34}},"dim":["","paragraph.129","inlineCode.2"],"code":"`window.*`"},{"id":"/root/children/129/children/3","type":"text","loc":{"start":38845,"end":38869,"line":{"s":664,"e":664,"code":["**actions.js** — shared `window.*` function registrations:"]},"column":{"s":34,"e":58}},"dim":["","paragraph.129","text.3"],"code":" function registrations:"},{"id":"/root/children/130","type":"code","loc":{"start":38871,"end":39264,"line":{"s":666,"e":679,"code":["```","","registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })","-> window.uploadCrud","-> window.uploadHook","","registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })","-> window.emitRepoIndex (REPO_AFFERENT)","-> window.emitRepoSync (REPO_AFFERENT)","-> window.emitRepoActualize (REPO_AFFERENT)","-> window.emitRepoMail (REPO_SERIAL)","-> window.webHookRepoStatus (webhook)","","```"]},"column":{"s":0,"e":3}},"dim":["","code.130"],"code":"```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```","symbName":"code","symbRange":[39266,39289],"symbRangeL":[null,682],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>"},{"id":"/root/children/131","type":"heading","loc":{"start":39266,"end":39289,"line":{"s":681,"e":681,"code":["### boot() in player.js"]},"column":{"s":0,"e":23}},"dim":["","heading.131"],"code":"### boot() in player.js","symbName":"heading","symbRange":[39291,40353],"symbRangeL":[681,709],"outerCode":"\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |","outerHtml":"\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>"},{"id":"/root/children/131/children/0","type":"text","loc":{"start":39270,"end":39289,"line":{"s":681,"e":681,"code":["### boot() in player.js"]},"column":{"s":4,"e":23}},"dim":["","heading.131","text.0"],"code":"boot() in player.js"},{"id":"/root/children/132","type":"code","loc":{"start":39291,"end":39815,"line":{"s":683,"e":696,"code":["```","","export async function boot(ssss, opts = {})","","1. Read config from <script id=\"app-config\"> JSON block","2. initRepolero(ssss, repoUrlBase, env)","3. Auto-set #\\_repoName text if element exists","4. initMqStomp (skipped if opts.noMq or no StompJs global)","5. registerUploadActions + registerRepoActions (if mqBridge)","6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)","7. opts.onBoot(result) callback for page-specific setup","   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }","","```"]},"column":{"s":0,"e":3}},"dim":["","code.132"],"code":"```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```","symbName":"code","symbRange":[39817,40470],"symbRangeL":[null,713],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>"},{"id":"/root/children/133","type":"paragraph","loc":{"start":39817,"end":40353,"line":{"s":698,"e":708,"code":["**opts:**","| Key | Type | Effect |","|---|---|---|","| `mapShow` | function | OpenLayers map module (passed to player) |","| `dashboard` | function | Dashboard module (passed to player) |","| `fileManager` | function | File manager module (passed to player) |","| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |","| `openLayers` | object | OpenLayers lib (passed to player) |","| `noMq` | bool | Skip MQ/STOMP init |","| `noPlayer` | bool | Skip player() call entirely |","| `onBoot` | function | Callback after boot, receives result object |"]},"column":{"s":0,"e":69}},"dim":["","paragraph.133"],"code":"**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |"},{"id":"/root/children/133/children/0","type":"strong","loc":{"start":39817,"end":39826,"line":{"s":698,"e":698,"code":["**opts:**"]},"column":{"s":0,"e":9}},"dim":["","paragraph.133","strong.0"],"code":"**opts:**"},{"id":"/root/children/133/children/0/children/0","type":"text","loc":{"start":39819,"end":39824,"line":{"s":698,"e":698,"code":["**opts:**"]},"column":{"s":2,"e":7}},"dim":["","paragraph.133","strong.0","text.0"],"code":"opts:"},{"id":"/root/children/133/children/1","type":"text","loc":{"start":39826,"end":39867,"line":{"s":698,"e":701,"code":["**opts:**","| Key | Type | Effect |","|---|---|---|","| `mapShow` | function | OpenLayers map module (passed to player) |"]},"column":{"s":9,"e":2}},"dim":["","paragraph.133","text.1"],"code":"\n| Key | Type | Effect |\n|---|---|---|\n| "},{"id":"/root/children/133/children/2","type":"inlineCode","loc":{"start":39867,"end":39876,"line":{"s":701,"e":701,"code":["| `mapShow` | function | OpenLayers map module (passed to player) |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.133","inlineCode.2"],"code":"`mapShow`"},{"id":"/root/children/133/children/3","type":"text","loc":{"start":39876,"end":39935,"line":{"s":701,"e":702,"code":["| `mapShow` | function | OpenLayers map module (passed to player) |","| `dashboard` | function | Dashboard module (passed to player) |"]},"column":{"s":11,"e":2}},"dim":["","paragraph.133","text.3"],"code":" | function | OpenLayers map module (passed to player) |\n| "},{"id":"/root/children/133/children/4","type":"inlineCode","loc":{"start":39935,"end":39946,"line":{"s":702,"e":702,"code":["| `dashboard` | function | Dashboard module (passed to player) |"]},"column":{"s":2,"e":13}},"dim":["","paragraph.133","inlineCode.4"],"code":"`dashboard`"},{"id":"/root/children/133/children/5","type":"text","loc":{"start":39946,"end":40000,"line":{"s":702,"e":703,"code":["| `dashboard` | function | Dashboard module (passed to player) |","| `fileManager` | function | File manager module (passed to player) |"]},"column":{"s":13,"e":2}},"dim":["","paragraph.133","text.5"],"code":" | function | Dashboard module (passed to player) |\n| "},{"id":"/root/children/133/children/6","type":"inlineCode","loc":{"start":40000,"end":40013,"line":{"s":703,"e":703,"code":["| `fileManager` | function | File manager module (passed to player) |"]},"column":{"s":2,"e":15}},"dim":["","paragraph.133","inlineCode.6"],"code":"`fileManager`"},{"id":"/root/children/133/children/7","type":"text","loc":{"start":40013,"end":40070,"line":{"s":703,"e":704,"code":["| `fileManager` | function | File manager module (passed to player) |","| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |"]},"column":{"s":15,"e":2}},"dim":["","paragraph.133","text.7"],"code":" | function | File manager module (passed to player) |\n| "},{"id":"/root/children/133/children/8","type":"inlineCode","loc":{"start":40070,"end":40075,"line":{"s":704,"e":704,"code":["| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.133","inlineCode.8"],"code":"`cm6`"},{"id":"/root/children/133/children/9","type":"text","loc":{"start":40075,"end":40115,"line":{"s":704,"e":704,"code":["| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |"]},"column":{"s":7,"e":47}},"dim":["","paragraph.133","text.9"],"code":" | object | CodeMirror 6 (falls back to "},{"id":"/root/children/133/children/10","type":"inlineCode","loc":{"start":40115,"end":40127,"line":{"s":704,"e":704,"code":["| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |"]},"column":{"s":47,"e":59}},"dim":["","paragraph.133","inlineCode.10"],"code":"`window.cm6`"},{"id":"/root/children/133/children/11","type":"text","loc":{"start":40127,"end":40133,"line":{"s":704,"e":705,"code":["| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |","| `openLayers` | object | OpenLayers lib (passed to player) |"]},"column":{"s":59,"e":2}},"dim":["","paragraph.133","text.11"],"code":") |\n| "},{"id":"/root/children/133/children/12","type":"inlineCode","loc":{"start":40133,"end":40145,"line":{"s":705,"e":705,"code":["| `openLayers` | object | OpenLayers lib (passed to player) |"]},"column":{"s":2,"e":14}},"dim":["","paragraph.133","inlineCode.12"],"code":"`openLayers`"},{"id":"/root/children/133/children/13","type":"text","loc":{"start":40145,"end":40195,"line":{"s":705,"e":706,"code":["| `openLayers` | object | OpenLayers lib (passed to player) |","| `noMq` | bool | Skip MQ/STOMP init |"]},"column":{"s":14,"e":2}},"dim":["","paragraph.133","text.13"],"code":" | object | OpenLayers lib (passed to player) |\n| "},{"id":"/root/children/133/children/14","type":"inlineCode","loc":{"start":40195,"end":40201,"line":{"s":706,"e":706,"code":["| `noMq` | bool | Skip MQ/STOMP init |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.133","inlineCode.14"],"code":"`noMq`"},{"id":"/root/children/133/children/15","type":"text","loc":{"start":40201,"end":40234,"line":{"s":706,"e":707,"code":["| `noMq` | bool | Skip MQ/STOMP init |","| `noPlayer` | bool | Skip player() call entirely |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.133","text.15"],"code":" | bool | Skip MQ/STOMP init |\n| "},{"id":"/root/children/133/children/16","type":"inlineCode","loc":{"start":40234,"end":40244,"line":{"s":707,"e":707,"code":["| `noPlayer` | bool | Skip player() call entirely |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.133","inlineCode.16"],"code":"`noPlayer`"},{"id":"/root/children/133/children/17","type":"text","loc":{"start":40244,"end":40286,"line":{"s":707,"e":708,"code":["| `noPlayer` | bool | Skip player() call entirely |","| `onBoot` | function | Callback after boot, receives result object |"]},"column":{"s":12,"e":2}},"dim":["","paragraph.133","text.17"],"code":" | bool | Skip player() call entirely |\n| "},{"id":"/root/children/133/children/18","type":"inlineCode","loc":{"start":40286,"end":40294,"line":{"s":708,"e":708,"code":["| `onBoot` | function | Callback after boot, receives result object |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.133","inlineCode.18"],"code":"`onBoot`"},{"id":"/root/children/133/children/19","type":"text","loc":{"start":40294,"end":40353,"line":{"s":708,"e":708,"code":["| `onBoot` | function | Callback after boot, receives result object |"]},"column":{"s":10,"e":69}},"dim":["","paragraph.133","text.19"],"code":" | function | Callback after boot, receives result object |"},{"id":"/root/children/134","type":"heading","loc":{"start":40355,"end":40403,"line":{"s":710,"e":710,"code":["### Config injection: `<script id=\"app-config\">`"]},"column":{"s":0,"e":48}},"dim":["","heading.134"],"code":"### Config injection: `<script id=\"app-config\">`","symbName":"heading","symbRange":[40405,41066],"symbRangeL":[710,726],"outerCode":"\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.","outerHtml":"\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>"},{"id":"/root/children/134/children/0","type":"text","loc":{"start":40359,"end":40377,"line":{"s":710,"e":710,"code":["### Config injection: `<script id=\"app-config\">`"]},"column":{"s":4,"e":22}},"dim":["","heading.134","text.0"],"code":"Config injection: "},{"id":"/root/children/134/children/1","type":"inlineCode","loc":{"start":40377,"end":40403,"line":{"s":710,"e":710,"code":["### Config injection: `<script id=\"app-config\">`"]},"column":{"s":22,"e":48}},"dim":["","heading.134","inlineCode.1"],"code":"`<script id=\"app-config\">`"},{"id":"/root/children/135","type":"paragraph","loc":{"start":40405,"end":40470,"line":{"s":712,"e":712,"code":["Each page has one jq expression that produces all config as JSON:"]},"column":{"s":0,"e":65}},"dim":["","paragraph.135"],"code":"Each page has one jq expression that produces all config as JSON:"},{"id":"/root/children/135/children/0","type":"text","loc":{"start":40405,"end":40470,"line":{"s":712,"e":712,"code":["Each page has one jq expression that produces all config as JSON:"]},"column":{"s":0,"e":65}},"dim":["","paragraph.135","text.0"],"code":"Each page has one jq expression that produces all config as JSON:"},{"id":"/root/children/136","type":"code","loc":{"start":40472,"end":40943,"line":{"s":714,"e":723,"code":["```html","<script id=\"app-config\" type=\"application/json\">","  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,","    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),","    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),","    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),","    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),","    pages: (.site.menu.pages|keys)} | tojson¦","</script>","```"]},"column":{"s":0,"e":3}},"dim":["","code.136"],"code":"```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```","symbName":"code","symbRange":[40945,41149],"symbRangeL":[null,730],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>"},{"id":"/root/children/137","type":"paragraph","loc":{"start":40945,"end":41066,"line":{"s":725,"e":725,"code":["Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it."]},"column":{"s":0,"e":121}},"dim":["","paragraph.137"],"code":"Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it."},{"id":"/root/children/137/children/0","type":"text","loc":{"start":40945,"end":40951,"line":{"s":725,"e":725,"code":["Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it."]},"column":{"s":0,"e":6}},"dim":["","paragraph.137","text.0"],"code":"Note: "},{"id":"/root/children/137/children/1","type":"inlineCode","loc":{"start":40951,"end":40968,"line":{"s":725,"e":725,"code":["Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it."]},"column":{"s":6,"e":23}},"dim":["","paragraph.137","inlineCode.1"],"code":"`.protocolRouter`"},{"id":"/root/children/137/children/2","type":"text","loc":{"start":40968,"end":41038,"line":{"s":725,"e":725,"code":["Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it."]},"column":{"s":23,"e":93}},"dim":["","paragraph.137","text.2"],"code":" is passed through as-is (already a JSON string from the jq context). "},{"id":"/root/children/137/children/3","type":"inlineCode","loc":{"start":41038,"end":41046,"line":{"s":725,"e":725,"code":["Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it."]},"column":{"s":93,"e":101}},"dim":["","paragraph.137","inlineCode.3"],"code":"`boot()`"},{"id":"/root/children/137/children/4","type":"text","loc":{"start":41046,"end":41066,"line":{"s":725,"e":725,"code":["Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it."]},"column":{"s":101,"e":121}},"dim":["","paragraph.137","text.4"],"code":" handles parsing it."},{"id":"/root/children/138","type":"heading","loc":{"start":41068,"end":41087,"line":{"s":727,"e":727,"code":["### Result per page"]},"column":{"s":0,"e":19}},"dim":["","heading.138"],"code":"### Result per page","symbName":"heading","symbRange":[41089,41983],"symbRangeL":[727,768],"outerCode":"\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```","outerHtml":"\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>"},{"id":"/root/children/138/children/0","type":"text","loc":{"start":41072,"end":41087,"line":{"s":727,"e":727,"code":["### Result per page"]},"column":{"s":4,"e":19}},"dim":["","heading.138","text.0"],"code":"Result per page"},{"id":"/root/children/139","type":"paragraph","loc":{"start":41089,"end":41149,"line":{"s":729,"e":729,"code":["**view.jq.html** — full app, all layers (Alpine.js via CDN):"]},"column":{"s":0,"e":60}},"dim":["","paragraph.139"],"code":"**view.jq.html** — full app, all layers (Alpine.js via CDN):"},{"id":"/root/children/139/children/0","type":"strong","loc":{"start":41089,"end":41105,"line":{"s":729,"e":729,"code":["**view.jq.html** — full app, all layers (Alpine.js via CDN):"]},"column":{"s":0,"e":16}},"dim":["","paragraph.139","strong.0"],"code":"**view.jq.html**"},{"id":"/root/children/139/children/0/children/0","type":"text","loc":{"start":41091,"end":41103,"line":{"s":729,"e":729,"code":["**view.jq.html** — full app, all layers (Alpine.js via CDN):"]},"column":{"s":2,"e":14}},"dim":["","paragraph.139","strong.0","text.0"],"code":"view.jq.html"},{"id":"/root/children/139/children/1","type":"text","loc":{"start":41105,"end":41149,"line":{"s":729,"e":729,"code":["**view.jq.html** — full app, all layers (Alpine.js via CDN):"]},"column":{"s":16,"e":60}},"dim":["","paragraph.139","text.1"],"code":" — full app, all layers (Alpine.js via CDN):"},{"id":"/root/children/140","type":"code","loc":{"start":41151,"end":41323,"line":{"s":731,"e":739,"code":["```html","<script src=\"./alpine-stores.js\"></script>","<script type=\"module\" async>","  import ssss from \"...\";","  import { boot } from \"./player.js\";","","  boot(ssss);","</script>","```"]},"column":{"s":0,"e":3}},"dim":["","code.140"],"code":"```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```","symbName":"code","symbRange":[41325,41379],"symbRangeL":[null,742],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>"},{"id":"/root/children/141","type":"paragraph","loc":{"start":41325,"end":41379,"line":{"s":741,"e":741,"code":["**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:"]},"column":{"s":0,"e":54}},"dim":["","paragraph.141"],"code":"**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:"},{"id":"/root/children/141/children/0","type":"strong","loc":{"start":41325,"end":41339,"line":{"s":741,"e":741,"code":["**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:"]},"column":{"s":0,"e":14}},"dim":["","paragraph.141","strong.0"],"code":"**rc.jq.html**"},{"id":"/root/children/141/children/0/children/0","type":"text","loc":{"start":41327,"end":41337,"line":{"s":741,"e":741,"code":["**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:"]},"column":{"s":2,"e":12}},"dim":["","paragraph.141","strong.0","text.0"],"code":"rc.jq.html"},{"id":"/root/children/141/children/1","type":"text","loc":{"start":41339,"end":41379,"line":{"s":741,"e":741,"code":["**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:"]},"column":{"s":14,"e":54}},"dim":["","paragraph.141","text.1"],"code":" — paper-only, MQ logging, D3 MSC chart:"},{"id":"/root/children/142","type":"code","loc":{"start":41381,"end":41768,"line":{"s":743,"e":756,"code":["```html","<script type=\"module\" async>","  import ssss from \"...\";","  import { boot } from \"./player.js\";","","  const { repoName, mqBridge, config } = await boot(ssss, {","    onBoot({ repoName, mqBridge, ssss, config }) {","      // rc: MQ fanout logging","      // rc: override webHookRepoStatus with processOutput","    },","  });","  // rc-specific: execSqlite, D3 MSC chart (stays inline)","</script>","```"]},"column":{"s":0,"e":3}},"dim":["","code.142"],"code":"```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```","symbName":"code","symbRange":[41770,41820],"symbRangeL":[null,759],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>"},{"id":"/root/children/143","type":"paragraph","loc":{"start":41770,"end":41820,"line":{"s":758,"e":758,"code":["**home.jq.html** — landing page, no player, no MQ:"]},"column":{"s":0,"e":50}},"dim":["","paragraph.143"],"code":"**home.jq.html** — landing page, no player, no MQ:"},{"id":"/root/children/143/children/0","type":"strong","loc":{"start":41770,"end":41786,"line":{"s":758,"e":758,"code":["**home.jq.html** — landing page, no player, no MQ:"]},"column":{"s":0,"e":16}},"dim":["","paragraph.143","strong.0"],"code":"**home.jq.html**"},{"id":"/root/children/143/children/0/children/0","type":"text","loc":{"start":41772,"end":41784,"line":{"s":758,"e":758,"code":["**home.jq.html** — landing page, no player, no MQ:"]},"column":{"s":2,"e":14}},"dim":["","paragraph.143","strong.0","text.0"],"code":"home.jq.html"},{"id":"/root/children/143/children/1","type":"text","loc":{"start":41786,"end":41820,"line":{"s":758,"e":758,"code":["**home.jq.html** — landing page, no player, no MQ:"]},"column":{"s":16,"e":50}},"dim":["","paragraph.143","text.1"],"code":" — landing page, no player, no MQ:"},{"id":"/root/children/144","type":"code","loc":{"start":41822,"end":41983,"line":{"s":760,"e":767,"code":["```html","<script type=\"module\" async>","  import ssss from \"...\";","  import { boot } from \"./player.js\";","","  boot(ssss, { noMq: true, noPlayer: true });","</script>","```"]},"column":{"s":0,"e":3}},"dim":["","code.144"],"code":"```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```","symbName":"code","symbRange":[41985,42476],"symbRangeL":[null,776],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>"},{"id":"/root/children/145","type":"heading","loc":{"start":41985,"end":42037,"line":{"s":769,"e":769,"code":["### TODO: Production optimizations (Brotli + Minify)"]},"column":{"s":0,"e":52}},"dim":["","heading.145"],"code":"### TODO: Production optimizations (Brotli + Minify)","symbName":"heading","symbRange":[42039,43041],"symbRangeL":[769,797],"outerCode":"\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.","outerHtml":"\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>"},{"id":"/root/children/145/children/0","type":"text","loc":{"start":41989,"end":42037,"line":{"s":769,"e":769,"code":["### TODO: Production optimizations (Brotli + Minify)"]},"column":{"s":4,"e":52}},"dim":["","heading.145","text.0"],"code":"TODO: Production optimizations (Brotli + Minify)"},{"id":"/root/children/146","type":"paragraph","loc":{"start":42039,"end":42177,"line":{"s":771,"e":771,"code":["The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`."]},"column":{"s":0,"e":138}},"dim":["","paragraph.146"],"code":"The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`."},{"id":"/root/children/146/children/0","type":"text","loc":{"start":42039,"end":42076,"line":{"s":771,"e":771,"code":["The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`."]},"column":{"s":0,"e":37}},"dim":["","paragraph.146","text.0"],"code":"The dev page is served by Caddy from "},{"id":"/root/children/146/children/1","type":"inlineCode","loc":{"start":42076,"end":42083,"line":{"s":771,"e":771,"code":["The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`."]},"column":{"s":37,"e":44}},"dim":["","paragraph.146","inlineCode.1"],"code":"`dist/`"},{"id":"/root/children/146/children/2","type":"text","loc":{"start":42083,"end":42096,"line":{"s":771,"e":771,"code":["The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`."]},"column":{"s":44,"e":57}},"dim":["","paragraph.146","text.2"],"code":". Caddyfile: "},{"id":"/root/children/146/children/3","type":"inlineCode","loc":{"start":42096,"end":42176,"line":{"s":771,"e":771,"code":["The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`."]},"column":{"s":57,"e":137}},"dim":["","paragraph.146","inlineCode.3"],"code":"`/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`"},{"id":"/root/children/146/children/4","type":"text","loc":{"start":42176,"end":42177,"line":{"s":771,"e":771,"code":["The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`."]},"column":{"s":137,"e":138}},"dim":["","paragraph.146","text.4"],"code":"."},{"id":"/root/children/147","type":"paragraph","loc":{"start":42179,"end":42330,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":0,"e":151}},"dim":["","paragraph.147"],"code":"**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."},{"id":"/root/children/147/children/0","type":"strong","loc":{"start":42179,"end":42201,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":0,"e":22}},"dim":["","paragraph.147","strong.0"],"code":"**Brotli compression**"},{"id":"/root/children/147/children/0/children/0","type":"text","loc":{"start":42181,"end":42199,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":2,"e":20}},"dim":["","paragraph.147","strong.0","text.0"],"code":"Brotli compression"},{"id":"/root/children/147/children/1","type":"text","loc":{"start":42201,"end":42219,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":22,"e":40}},"dim":["","paragraph.147","text.1"],"code":" — Currently uses "},{"id":"/root/children/147/children/2","type":"inlineCode","loc":{"start":42219,"end":42237,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":40,"e":58}},"dim":["","paragraph.147","inlineCode.2"],"code":"`encode gzip zstd`"},{"id":"/root/children/147/children/3","type":"text","loc":{"start":42237,"end":42252,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":58,"e":73}},"dim":["","paragraph.147","text.3"],"code":" (line 8). Add "},{"id":"/root/children/147/children/4","type":"inlineCode","loc":{"start":42252,"end":42256,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":73,"e":77}},"dim":["","paragraph.147","inlineCode.4"],"code":"`br`"},{"id":"/root/children/147/children/5","type":"text","loc":{"start":42256,"end":42258,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":77,"e":79}},"dim":["","paragraph.147","text.5"],"code":": "},{"id":"/root/children/147/children/6","type":"inlineCode","loc":{"start":42258,"end":42279,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":79,"e":100}},"dim":["","paragraph.147","inlineCode.6"],"code":"`encode br gzip zstd`"},{"id":"/root/children/147/children/7","type":"text","loc":{"start":42279,"end":42330,"line":{"s":773,"e":773,"code":["**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone."]},"column":{"s":100,"e":151}},"dim":["","paragraph.147","text.7"],"code":". One-word change, ~15-20% smaller than gzip alone."},{"id":"/root/children/148","type":"paragraph","loc":{"start":42332,"end":42476,"line":{"s":775,"e":775,"code":["**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:"]},"column":{"s":0,"e":144}},"dim":["","paragraph.148"],"code":"**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:"},{"id":"/root/children/148/children/0","type":"strong","loc":{"start":42332,"end":42351,"line":{"s":775,"e":775,"code":["**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:"]},"column":{"s":0,"e":19}},"dim":["","paragraph.148","strong.0"],"code":"**JS minification**"},{"id":"/root/children/148/children/0/children/0","type":"text","loc":{"start":42334,"end":42349,"line":{"s":775,"e":775,"code":["**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:"]},"column":{"s":2,"e":17}},"dim":["","paragraph.148","strong.0","text.0"],"code":"JS minification"},{"id":"/root/children/148/children/1","type":"text","loc":{"start":42351,"end":42476,"line":{"s":775,"e":775,"code":["**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:"]},"column":{"s":19,"e":144}},"dim":["","paragraph.148","text.1"],"code":" — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:"},{"id":"/root/children/149","type":"code","loc":{"start":42478,"end":42661,"line":{"s":777,"e":783,"code":["```sh","# install esbuild (once)","npm i -g esbuild","","# minify all .js files in current folder and below","find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;","```"]},"column":{"s":0,"e":3}},"dim":["","code.149"],"code":"```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```","symbName":"code","symbRange":[42663,42725],"symbRangeL":[null,786],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>"},{"id":"/root/children/150","type":"paragraph","loc":{"start":42663,"end":42725,"line":{"s":785,"e":785,"code":["**Combined** (minify + pre-compress for Caddy static serving):"]},"column":{"s":0,"e":62}},"dim":["","paragraph.150"],"code":"**Combined** (minify + pre-compress for Caddy static serving):"},{"id":"/root/children/150/children/0","type":"strong","loc":{"start":42663,"end":42675,"line":{"s":785,"e":785,"code":["**Combined** (minify + pre-compress for Caddy static serving):"]},"column":{"s":0,"e":12}},"dim":["","paragraph.150","strong.0"],"code":"**Combined**"},{"id":"/root/children/150/children/0/children/0","type":"text","loc":{"start":42665,"end":42673,"line":{"s":785,"e":785,"code":["**Combined** (minify + pre-compress for Caddy static serving):"]},"column":{"s":2,"e":10}},"dim":["","paragraph.150","strong.0","text.0"],"code":"Combined"},{"id":"/root/children/150/children/1","type":"text","loc":{"start":42675,"end":42725,"line":{"s":785,"e":785,"code":["**Combined** (minify + pre-compress for Caddy static serving):"]},"column":{"s":12,"e":62}},"dim":["","paragraph.150","text.1"],"code":" (minify + pre-compress for Caddy static serving):"},{"id":"/root/children/151","type":"code","loc":{"start":42727,"end":42955,"line":{"s":787,"e":794,"code":["```sh","# minify + generate .br files","find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;","find . -name '*.js' -exec brotli -f {} \\;","","# then in Caddyfile, use:","# file_server { precompressed br gzip }","```"]},"column":{"s":0,"e":3}},"dim":["","code.151"],"code":"```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```","symbName":"code","symbRange":[42957,54014],"symbRangeL":[null,905],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done\n\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done\n\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps\n\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done\n\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done\n\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use\n\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed\n\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>\n\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>\n\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>\n\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>"},{"id":"/root/children/152","type":"paragraph","loc":{"start":42957,"end":43041,"line":{"s":796,"e":796,"code":["Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`."]},"column":{"s":0,"e":84}},"dim":["","paragraph.152"],"code":"Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`."},{"id":"/root/children/152/children/0","type":"text","loc":{"start":42957,"end":43008,"line":{"s":796,"e":796,"code":["Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`."]},"column":{"s":0,"e":51}},"dim":["","paragraph.152","text.0"],"code":"Note: these commands modify files in-place. Run on "},{"id":"/root/children/152/children/1","type":"inlineCode","loc":{"start":43008,"end":43015,"line":{"s":796,"e":796,"code":["Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`."]},"column":{"s":51,"e":58}},"dim":["","paragraph.152","inlineCode.1"],"code":"`dist/`"},{"id":"/root/children/152/children/2","type":"text","loc":{"start":43015,"end":43031,"line":{"s":796,"e":796,"code":["Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`."]},"column":{"s":58,"e":74}},"dim":["","paragraph.152","text.2"],"code":" copies, not on "},{"id":"/root/children/152/children/3","type":"inlineCode","loc":{"start":43031,"end":43040,"line":{"s":796,"e":796,"code":["Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`."]},"column":{"s":74,"e":83}},"dim":["","paragraph.152","inlineCode.3"],"code":"`source/`"},{"id":"/root/children/152/children/4","type":"text","loc":{"start":43040,"end":43041,"line":{"s":796,"e":796,"code":["Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`."]},"column":{"s":83,"e":84}},"dim":["","paragraph.152","text.4"],"code":"."},{"id":"/root/children/153","type":"heading","loc":{"start":43043,"end":43073,"line":{"s":798,"e":798,"code":["# Split player.js into modules"]},"column":{"s":0,"e":30}},"dim":["","heading.153"],"code":"# Split player.js into modules","symbName":"heading","symbRange":[43075,70981],"symbRangeL":[798,799],"outerCode":"","outerHtml":""},{"id":"/root/children/153/children/0","type":"text","loc":{"start":43045,"end":43073,"line":{"s":798,"e":798,"code":["# Split player.js into modules"]},"column":{"s":2,"e":30}},"dim":["","heading.153","text.0"],"code":"Split player.js into modules"},{"id":"/root/children/154","type":"heading","loc":{"start":43075,"end":43082,"line":{"s":800,"e":800,"code":["## Done"]},"column":{"s":0,"e":7}},"dim":["","heading.154"],"code":"## Done","symbName":"heading","symbRange":[43084,44316],"symbRangeL":[800,819],"outerCode":"\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()","outerHtml":"\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>"},{"id":"/root/children/154/children/0","type":"text","loc":{"start":43078,"end":43082,"line":{"s":800,"e":800,"code":["## Done"]},"column":{"s":3,"e":7}},"dim":["","heading.154","text.0"],"code":"Done"},{"id":"/root/children/155","type":"list","loc":{"start":43084,"end":44316,"line":{"s":802,"e":818,"code":["- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys","- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper","- [x] `player-state.js` (~43 lines) — resetState, resetMachine","- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile","- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus","- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree","- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper","- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit","- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace","- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)","  - imports from all 9 modules","  - keeps: boot(), player() shell, notyf setup, PROXY assembly","  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace","  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks","  - dropped dead code (keyNav), commented-out blocks, duplicate functions","  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload","  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"]},"column":{"s":0,"e":61}},"dim":["","list.155"],"code":"- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()","symbName":"list","symbRange":[44318,44352],"symbRangeL":[802,823],"outerCode":"- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done","outerHtml":"<ul><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>"},{"id":"/root/children/155/children/0","type":"listItem","loc":{"start":43084,"end":43160,"line":{"s":802,"e":802,"code":["- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys"]},"column":{"s":0,"e":76}},"dim":["","list.155","listItem.0"],"code":"- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys"},{"id":"/root/children/155/children/0/children/0","type":"paragraph","loc":{"start":43086,"end":43160,"line":{"s":802,"e":802,"code":["- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys"]},"column":{"s":2,"e":76}},"dim":["","list.155","listItem.0","paragraph.0"],"code":"[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys"},{"id":"/root/children/155/children/0/children/0/children/0","type":"text","loc":{"start":43086,"end":43090,"line":{"s":802,"e":802,"code":["- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.0","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/0/children/0/children/1","type":"inlineCode","loc":{"start":43090,"end":43107,"line":{"s":802,"e":802,"code":["- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys"]},"column":{"s":6,"e":23}},"dim":["","list.155","listItem.0","paragraph.0","inlineCode.1"],"code":"`player-utils.js`"},{"id":"/root/children/155/children/0/children/0/children/2","type":"text","loc":{"start":43107,"end":43160,"line":{"s":802,"e":802,"code":["- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys"]},"column":{"s":23,"e":76}},"dim":["","list.155","listItem.0","paragraph.0","text.2"],"code":" (~265 lines) — constants, route/URI helpers, hotkeys"},{"id":"/root/children/155/children/1","type":"listItem","loc":{"start":43161,"end":43242,"line":{"s":803,"e":803,"code":["- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper"]},"column":{"s":0,"e":81}},"dim":["","list.155","listItem.1"],"code":"- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper"},{"id":"/root/children/155/children/1/children/0","type":"paragraph","loc":{"start":43163,"end":43242,"line":{"s":803,"e":803,"code":["- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper"]},"column":{"s":2,"e":81}},"dim":["","list.155","listItem.1","paragraph.0"],"code":"[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper"},{"id":"/root/children/155/children/1/children/0/children/0","type":"text","loc":{"start":43163,"end":43167,"line":{"s":803,"e":803,"code":["- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.1","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/1/children/0/children/1","type":"inlineCode","loc":{"start":43167,"end":43186,"line":{"s":803,"e":803,"code":["- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper"]},"column":{"s":6,"e":25}},"dim":["","list.155","listItem.1","paragraph.0","inlineCode.1"],"code":"`player-webhook.js`"},{"id":"/root/children/155/children/1/children/0/children/2","type":"text","loc":{"start":43186,"end":43242,"line":{"s":803,"e":803,"code":["- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper"]},"column":{"s":25,"e":81}},"dim":["","list.155","listItem.1","paragraph.0","text.2"],"code":" (~145 lines) — isWebHookOn, upload, editSave, savePaper"},{"id":"/root/children/155/children/2","type":"listItem","loc":{"start":43243,"end":43305,"line":{"s":804,"e":804,"code":["- [x] `player-state.js` (~43 lines) — resetState, resetMachine"]},"column":{"s":0,"e":62}},"dim":["","list.155","listItem.2"],"code":"- [x] `player-state.js` (~43 lines) — resetState, resetMachine"},{"id":"/root/children/155/children/2/children/0","type":"paragraph","loc":{"start":43245,"end":43305,"line":{"s":804,"e":804,"code":["- [x] `player-state.js` (~43 lines) — resetState, resetMachine"]},"column":{"s":2,"e":62}},"dim":["","list.155","listItem.2","paragraph.0"],"code":"[x] `player-state.js` (~43 lines) — resetState, resetMachine"},{"id":"/root/children/155/children/2/children/0/children/0","type":"text","loc":{"start":43245,"end":43249,"line":{"s":804,"e":804,"code":["- [x] `player-state.js` (~43 lines) — resetState, resetMachine"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.2","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/2/children/0/children/1","type":"inlineCode","loc":{"start":43249,"end":43266,"line":{"s":804,"e":804,"code":["- [x] `player-state.js` (~43 lines) — resetState, resetMachine"]},"column":{"s":6,"e":23}},"dim":["","list.155","listItem.2","paragraph.0","inlineCode.1"],"code":"`player-state.js`"},{"id":"/root/children/155/children/2/children/0/children/2","type":"text","loc":{"start":43266,"end":43305,"line":{"s":804,"e":804,"code":["- [x] `player-state.js` (~43 lines) — resetState, resetMachine"]},"column":{"s":23,"e":62}},"dim":["","list.155","listItem.2","paragraph.0","text.2"],"code":" (~43 lines) — resetState, resetMachine"},{"id":"/root/children/155/children/3","type":"listItem","loc":{"start":43306,"end":43378,"line":{"s":805,"e":805,"code":["- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"]},"column":{"s":0,"e":72}},"dim":["","list.155","listItem.3"],"code":"- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"},{"id":"/root/children/155/children/3/children/0","type":"paragraph","loc":{"start":43308,"end":43378,"line":{"s":805,"e":805,"code":["- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"]},"column":{"s":2,"e":72}},"dim":["","list.155","listItem.3","paragraph.0"],"code":"[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"},{"id":"/root/children/155/children/3/children/0/children/0","type":"text","loc":{"start":43308,"end":43312,"line":{"s":805,"e":805,"code":["- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.3","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/3/children/0/children/1","type":"inlineCode","loc":{"start":43312,"end":43326,"line":{"s":805,"e":805,"code":["- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"]},"column":{"s":6,"e":20}},"dim":["","list.155","listItem.3","paragraph.0","inlineCode.1"],"code":"`player-mq.js`"},{"id":"/root/children/155/children/3/children/0/children/2","type":"text","loc":{"start":43326,"end":43378,"line":{"s":805,"e":805,"code":["- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"]},"column":{"s":20,"e":72}},"dim":["","list.155","listItem.3","paragraph.0","text.2"],"code":" (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile"},{"id":"/root/children/155/children/4","type":"listItem","loc":{"start":43379,"end":43476,"line":{"s":806,"e":806,"code":["- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"]},"column":{"s":0,"e":97}},"dim":["","list.155","listItem.4"],"code":"- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"},{"id":"/root/children/155/children/4/children/0","type":"paragraph","loc":{"start":43381,"end":43476,"line":{"s":806,"e":806,"code":["- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"]},"column":{"s":2,"e":97}},"dim":["","list.155","listItem.4","paragraph.0"],"code":"[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"},{"id":"/root/children/155/children/4/children/0/children/0","type":"text","loc":{"start":43381,"end":43385,"line":{"s":806,"e":806,"code":["- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.4","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/4/children/0/children/1","type":"inlineCode","loc":{"start":43385,"end":43402,"line":{"s":806,"e":806,"code":["- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"]},"column":{"s":6,"e":23}},"dim":["","list.155","listItem.4","paragraph.0","inlineCode.1"],"code":"`player-glass.js`"},{"id":"/root/children/155/children/4/children/0/children/2","type":"text","loc":{"start":43402,"end":43476,"line":{"s":806,"e":806,"code":["- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"]},"column":{"s":23,"e":97}},"dim":["","list.155","listItem.4","paragraph.0","text.2"],"code":" (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus"},{"id":"/root/children/155/children/5","type":"listItem","loc":{"start":43477,"end":43572,"line":{"s":807,"e":807,"code":["- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"]},"column":{"s":0,"e":95}},"dim":["","list.155","listItem.5"],"code":"- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"},{"id":"/root/children/155/children/5/children/0","type":"paragraph","loc":{"start":43479,"end":43572,"line":{"s":807,"e":807,"code":["- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"]},"column":{"s":2,"e":95}},"dim":["","list.155","listItem.5","paragraph.0"],"code":"[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"},{"id":"/root/children/155/children/5/children/0/children/0","type":"text","loc":{"start":43479,"end":43483,"line":{"s":807,"e":807,"code":["- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.5","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/5/children/0/children/1","type":"inlineCode","loc":{"start":43483,"end":43501,"line":{"s":807,"e":807,"code":["- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"]},"column":{"s":6,"e":24}},"dim":["","list.155","listItem.5","paragraph.0","inlineCode.1"],"code":"`player-tweaks.js`"},{"id":"/root/children/155/children/5/children/0/children/2","type":"text","loc":{"start":43501,"end":43572,"line":{"s":807,"e":807,"code":["- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"]},"column":{"s":24,"e":95}},"dim":["","list.155","listItem.5","paragraph.0","text.2"],"code":" (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree"},{"id":"/root/children/155/children/6","type":"listItem","loc":{"start":43573,"end":43654,"line":{"s":808,"e":808,"code":["- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"]},"column":{"s":0,"e":81}},"dim":["","list.155","listItem.6"],"code":"- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"},{"id":"/root/children/155/children/6/children/0","type":"paragraph","loc":{"start":43575,"end":43654,"line":{"s":808,"e":808,"code":["- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"]},"column":{"s":2,"e":81}},"dim":["","list.155","listItem.6","paragraph.0"],"code":"[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"},{"id":"/root/children/155/children/6/children/0/children/0","type":"text","loc":{"start":43575,"end":43579,"line":{"s":808,"e":808,"code":["- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.6","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/6/children/0/children/1","type":"inlineCode","loc":{"start":43579,"end":43596,"line":{"s":808,"e":808,"code":["- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"]},"column":{"s":6,"e":23}},"dim":["","list.155","listItem.6","paragraph.0","inlineCode.1"],"code":"`player-paper.js`"},{"id":"/root/children/155/children/6/children/0/children/2","type":"text","loc":{"start":43596,"end":43654,"line":{"s":808,"e":808,"code":["- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"]},"column":{"s":23,"e":81}},"dim":["","list.155","listItem.6","paragraph.0","text.2"],"code":" (~426 lines) — eSetOnOff, createFilterPapers, renderPaper"},{"id":"/root/children/155/children/7","type":"listItem","loc":{"start":43655,"end":43715,"line":{"s":809,"e":809,"code":["- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit"]},"column":{"s":0,"e":60}},"dim":["","list.155","listItem.7"],"code":"- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit"},{"id":"/root/children/155/children/7/children/0","type":"paragraph","loc":{"start":43657,"end":43715,"line":{"s":809,"e":809,"code":["- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit"]},"column":{"s":2,"e":60}},"dim":["","list.155","listItem.7","paragraph.0"],"code":"[x] `player-edit.js` (~233 lines) — renderSave, renderEdit"},{"id":"/root/children/155/children/7/children/0/children/0","type":"text","loc":{"start":43657,"end":43661,"line":{"s":809,"e":809,"code":["- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.7","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/7/children/0/children/1","type":"inlineCode","loc":{"start":43661,"end":43677,"line":{"s":809,"e":809,"code":["- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit"]},"column":{"s":6,"e":22}},"dim":["","list.155","listItem.7","paragraph.0","inlineCode.1"],"code":"`player-edit.js`"},{"id":"/root/children/155/children/7/children/0/children/2","type":"text","loc":{"start":43677,"end":43715,"line":{"s":809,"e":809,"code":["- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit"]},"column":{"s":22,"e":60}},"dim":["","list.155","listItem.7","paragraph.0","text.2"],"code":" (~233 lines) — renderSave, renderEdit"},{"id":"/root/children/155/children/8","type":"listItem","loc":{"start":43716,"end":43778,"line":{"s":810,"e":810,"code":["- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace"]},"column":{"s":0,"e":62}},"dim":["","list.155","listItem.8"],"code":"- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace"},{"id":"/root/children/155/children/8/children/0","type":"paragraph","loc":{"start":43718,"end":43778,"line":{"s":810,"e":810,"code":["- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace"]},"column":{"s":2,"e":62}},"dim":["","list.155","listItem.8","paragraph.0"],"code":"[x] `player-space.js` (~253 lines) — resetSpace, renderSpace"},{"id":"/root/children/155/children/8/children/0/children/0","type":"text","loc":{"start":43718,"end":43722,"line":{"s":810,"e":810,"code":["- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.8","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/8/children/0/children/1","type":"inlineCode","loc":{"start":43722,"end":43739,"line":{"s":810,"e":810,"code":["- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace"]},"column":{"s":6,"e":23}},"dim":["","list.155","listItem.8","paragraph.0","inlineCode.1"],"code":"`player-space.js`"},{"id":"/root/children/155/children/8/children/0/children/2","type":"text","loc":{"start":43739,"end":43778,"line":{"s":810,"e":810,"code":["- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace"]},"column":{"s":23,"e":62}},"dim":["","list.155","listItem.8","paragraph.0","text.2"],"code":" (~253 lines) — resetSpace, renderSpace"},{"id":"/root/children/155/children/9","type":"listItem","loc":{"start":43779,"end":44316,"line":{"s":811,"e":818,"code":["- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)","  - imports from all 9 modules","  - keeps: boot(), player() shell, notyf setup, PROXY assembly","  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace","  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks","  - dropped dead code (keyNav), commented-out blocks, duplicate functions","  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload","  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"]},"column":{"s":0,"e":61}},"dim":["","list.155","listItem.9"],"code":"- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"},{"id":"/root/children/155/children/9/children/0","type":"paragraph","loc":{"start":43781,"end":43847,"line":{"s":811,"e":811,"code":["- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)"]},"column":{"s":2,"e":68}},"dim":["","list.155","listItem.9","paragraph.0"],"code":"[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)"},{"id":"/root/children/155/children/9/children/0/children/0","type":"text","loc":{"start":43781,"end":43785,"line":{"s":811,"e":811,"code":["- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)"]},"column":{"s":2,"e":6}},"dim":["","list.155","listItem.9","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/155/children/9/children/0/children/1","type":"inlineCode","loc":{"start":43785,"end":43796,"line":{"s":811,"e":811,"code":["- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)"]},"column":{"s":6,"e":17}},"dim":["","list.155","listItem.9","paragraph.0","inlineCode.1"],"code":"`player.js`"},{"id":"/root/children/155/children/9/children/0/children/2","type":"text","loc":{"start":43796,"end":43847,"line":{"s":811,"e":811,"code":["- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)"]},"column":{"s":17,"e":68}},"dim":["","list.155","listItem.9","paragraph.0","text.2"],"code":" rewritten as slim orchestrator (3672 → 1022 lines)"},{"id":"/root/children/155/children/9/children/1","type":"list","loc":{"start":43850,"end":44316,"line":{"s":812,"e":818,"code":["  - imports from all 9 modules","  - keeps: boot(), player() shell, notyf setup, PROXY assembly","  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace","  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks","  - dropped dead code (keyNav), commented-out blocks, duplicate functions","  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload","  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"]},"column":{"s":2,"e":61}},"dim":["","list.155","listItem.9","list.1"],"code":"- imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()","symbName":"list","symbRange":[null,null],"symbRangeL":[812,818],"outerCode":"  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload","outerHtml":"<ul><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li></ul>"},{"id":"/root/children/155/children/9/children/1/children/0","type":"listItem","loc":{"start":43850,"end":43878,"line":{"s":812,"e":812,"code":["  - imports from all 9 modules"]},"column":{"s":2,"e":30}},"dim":["","list.155","listItem.9","list.1","listItem.0"],"code":"- imports from all 9 modules"},{"id":"/root/children/155/children/9/children/1/children/0/children/0","type":"paragraph","loc":{"start":43852,"end":43878,"line":{"s":812,"e":812,"code":["  - imports from all 9 modules"]},"column":{"s":4,"e":30}},"dim":["","list.155","listItem.9","list.1","listItem.0","paragraph.0"],"code":"imports from all 9 modules"},{"id":"/root/children/155/children/9/children/1/children/0/children/0/children/0","type":"text","loc":{"start":43852,"end":43878,"line":{"s":812,"e":812,"code":["  - imports from all 9 modules"]},"column":{"s":4,"e":30}},"dim":["","list.155","listItem.9","list.1","listItem.0","paragraph.0","text.0"],"code":"imports from all 9 modules"},{"id":"/root/children/155/children/9/children/1/children/1","type":"listItem","loc":{"start":43881,"end":43941,"line":{"s":813,"e":813,"code":["  - keeps: boot(), player() shell, notyf setup, PROXY assembly"]},"column":{"s":2,"e":62}},"dim":["","list.155","listItem.9","list.1","listItem.1"],"code":"- keeps: boot(), player() shell, notyf setup, PROXY assembly"},{"id":"/root/children/155/children/9/children/1/children/1/children/0","type":"paragraph","loc":{"start":43883,"end":43941,"line":{"s":813,"e":813,"code":["  - keeps: boot(), player() shell, notyf setup, PROXY assembly"]},"column":{"s":4,"e":62}},"dim":["","list.155","listItem.9","list.1","listItem.1","paragraph.0"],"code":"keeps: boot(), player() shell, notyf setup, PROXY assembly"},{"id":"/root/children/155/children/9/children/1/children/1/children/0/children/0","type":"text","loc":{"start":43883,"end":43941,"line":{"s":813,"e":813,"code":["  - keeps: boot(), player() shell, notyf setup, PROXY assembly"]},"column":{"s":4,"e":62}},"dim":["","list.155","listItem.9","list.1","listItem.1","paragraph.0","text.0"],"code":"keeps: boot(), player() shell, notyf setup, PROXY assembly"},{"id":"/root/children/155/children/9/children/1/children/2","type":"listItem","loc":{"start":43944,"end":44033,"line":{"s":814,"e":814,"code":["  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace"]},"column":{"s":2,"e":91}},"dim":["","list.155","listItem.9","list.1","listItem.2"],"code":"- renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace"},{"id":"/root/children/155/children/9/children/1/children/2/children/0","type":"paragraph","loc":{"start":43946,"end":44033,"line":{"s":814,"e":814,"code":["  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace"]},"column":{"s":4,"e":91}},"dim":["","list.155","listItem.9","list.1","listItem.2","paragraph.0"],"code":"renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace"},{"id":"/root/children/155/children/9/children/1/children/2/children/0/children/0","type":"text","loc":{"start":43946,"end":44033,"line":{"s":814,"e":814,"code":["  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace"]},"column":{"s":4,"e":91}},"dim":["","list.155","listItem.9","list.1","listItem.2","paragraph.0","text.0"],"code":"renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace"},{"id":"/root/children/155/children/9/children/1/children/3","type":"listItem","loc":{"start":44036,"end":44117,"line":{"s":815,"e":815,"code":["  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks"]},"column":{"s":2,"e":83}},"dim":["","list.155","listItem.9","list.1","listItem.3"],"code":"- bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks"},{"id":"/root/children/155/children/9/children/1/children/3/children/0","type":"paragraph","loc":{"start":44038,"end":44117,"line":{"s":815,"e":815,"code":["  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks"]},"column":{"s":4,"e":83}},"dim":["","list.155","listItem.9","list.1","listItem.3","paragraph.0"],"code":"bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks"},{"id":"/root/children/155/children/9/children/1/children/3/children/0/children/0","type":"text","loc":{"start":44038,"end":44117,"line":{"s":815,"e":815,"code":["  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks"]},"column":{"s":4,"e":83}},"dim":["","list.155","listItem.9","list.1","listItem.3","paragraph.0","text.0"],"code":"bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks"},{"id":"/root/children/155/children/9/children/1/children/4","type":"listItem","loc":{"start":44120,"end":44191,"line":{"s":816,"e":816,"code":["  - dropped dead code (keyNav), commented-out blocks, duplicate functions"]},"column":{"s":2,"e":73}},"dim":["","list.155","listItem.9","list.1","listItem.4"],"code":"- dropped dead code (keyNav), commented-out blocks, duplicate functions"},{"id":"/root/children/155/children/9/children/1/children/4/children/0","type":"paragraph","loc":{"start":44122,"end":44191,"line":{"s":816,"e":816,"code":["  - dropped dead code (keyNav), commented-out blocks, duplicate functions"]},"column":{"s":4,"e":73}},"dim":["","list.155","listItem.9","list.1","listItem.4","paragraph.0"],"code":"dropped dead code (keyNav), commented-out blocks, duplicate functions"},{"id":"/root/children/155/children/9/children/1/children/4/children/0/children/0","type":"text","loc":{"start":44122,"end":44191,"line":{"s":816,"e":816,"code":["  - dropped dead code (keyNav), commented-out blocks, duplicate functions"]},"column":{"s":4,"e":73}},"dim":["","list.155","listItem.9","list.1","listItem.4","paragraph.0","text.0"],"code":"dropped dead code (keyNav), commented-out blocks, duplicate functions"},{"id":"/root/children/155/children/9/children/1/children/5","type":"listItem","loc":{"start":44194,"end":44254,"line":{"s":817,"e":817,"code":["  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload"]},"column":{"s":2,"e":62}},"dim":["","list.155","listItem.9","list.1","listItem.5"],"code":"- reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload"},{"id":"/root/children/155/children/9/children/1/children/5/children/0","type":"paragraph","loc":{"start":44196,"end":44254,"line":{"s":817,"e":817,"code":["  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload"]},"column":{"s":4,"e":62}},"dim":["","list.155","listItem.9","list.1","listItem.5","paragraph.0"],"code":"reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload"},{"id":"/root/children/155/children/9/children/1/children/5/children/0/children/0","type":"text","loc":{"start":44196,"end":44254,"line":{"s":817,"e":817,"code":["  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload"]},"column":{"s":4,"e":62}},"dim":["","list.155","listItem.9","list.1","listItem.5","paragraph.0","text.0"],"code":"reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload"},{"id":"/root/children/155/children/9/children/1/children/6","type":"listItem","loc":{"start":44257,"end":44316,"line":{"s":818,"e":818,"code":["  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"]},"column":{"s":2,"e":61}},"dim":["","list.155","listItem.9","list.1","listItem.6"],"code":"- player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"},{"id":"/root/children/155/children/9/children/1/children/6/children/0","type":"paragraph","loc":{"start":44259,"end":44316,"line":{"s":818,"e":818,"code":["  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"]},"column":{"s":4,"e":61}},"dim":["","list.155","listItem.9","list.1","listItem.6","paragraph.0"],"code":"player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"},{"id":"/root/children/155/children/9/children/1/children/6/children/0/children/0","type":"text","loc":{"start":44259,"end":44316,"line":{"s":818,"e":818,"code":["  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"]},"column":{"s":4,"e":61}},"dim":["","list.155","listItem.9","list.1","listItem.6","paragraph.0","text.0"],"code":"player-mq.js updated to use PROXY.reloadCurrentPaper.fn()"},{"id":"/root/children/156","type":"heading","loc":{"start":44318,"end":44343,"line":{"s":820,"e":820,"code":["# Touchbar & Double-Touch"]},"column":{"s":0,"e":25}},"dim":["","heading.156"],"code":"# Touchbar & Double-Touch","symbName":"heading","symbRange":[44345,70981],"symbRangeL":[820,821],"outerCode":"","outerHtml":""},{"id":"/root/children/156/children/0","type":"text","loc":{"start":44320,"end":44343,"line":{"s":820,"e":820,"code":["# Touchbar & Double-Touch"]},"column":{"s":2,"e":25}},"dim":["","heading.156","text.0"],"code":"Touchbar & Double-Touch"},{"id":"/root/children/157","type":"heading","loc":{"start":44345,"end":44352,"line":{"s":822,"e":822,"code":["## Done"]},"column":{"s":0,"e":7}},"dim":["","heading.157"],"code":"## Done","symbName":"heading","symbRange":[44354,46326],"symbRangeL":[822,839],"outerCode":"\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.","outerHtml":"\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>"},{"id":"/root/children/157/children/0","type":"text","loc":{"start":44348,"end":44352,"line":{"s":822,"e":822,"code":["## Done"]},"column":{"s":3,"e":7}},"dim":["","heading.157","text.0"],"code":"Done"},{"id":"/root/children/158","type":"list","loc":{"start":44354,"end":46326,"line":{"s":824,"e":838,"code":["- [x] Alpine.js replaced Litedom for touchbar","- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)","- [x] Native click delegation instead of Alpine @click (avoids double-fire)","- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`","- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)","- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips","- [x] `data-doubletouch` only rendered when `action_extra` exists","- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)","- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)","- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar","- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:","  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)","  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown","  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`","- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":0,"e":435}},"dim":["","list.158"],"code":"- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.","symbName":"list","symbRange":[46328,46341],"symbRangeL":[824,841],"outerCode":"- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps","outerHtml":"<ul><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>"},{"id":"/root/children/158/children/0","type":"listItem","loc":{"start":44354,"end":44399,"line":{"s":824,"e":824,"code":["- [x] Alpine.js replaced Litedom for touchbar"]},"column":{"s":0,"e":45}},"dim":["","list.158","listItem.0"],"code":"- [x] Alpine.js replaced Litedom for touchbar"},{"id":"/root/children/158/children/0/children/0","type":"paragraph","loc":{"start":44356,"end":44399,"line":{"s":824,"e":824,"code":["- [x] Alpine.js replaced Litedom for touchbar"]},"column":{"s":2,"e":45}},"dim":["","list.158","listItem.0","paragraph.0"],"code":"[x] Alpine.js replaced Litedom for touchbar"},{"id":"/root/children/158/children/0/children/0/children/0","type":"text","loc":{"start":44356,"end":44399,"line":{"s":824,"e":824,"code":["- [x] Alpine.js replaced Litedom for touchbar"]},"column":{"s":2,"e":45}},"dim":["","list.158","listItem.0","paragraph.0","text.0"],"code":"[x] Alpine.js replaced Litedom for touchbar"},{"id":"/root/children/158/children/1","type":"listItem","loc":{"start":44400,"end":44495,"line":{"s":825,"e":825,"code":["- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"]},"column":{"s":0,"e":95}},"dim":["","list.158","listItem.1"],"code":"- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"},{"id":"/root/children/158/children/1/children/0","type":"paragraph","loc":{"start":44402,"end":44495,"line":{"s":825,"e":825,"code":["- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"]},"column":{"s":2,"e":95}},"dim":["","list.158","listItem.1","paragraph.0"],"code":"[x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"},{"id":"/root/children/158/children/1/children/0/children/0","type":"text","loc":{"start":44402,"end":44457,"line":{"s":825,"e":825,"code":["- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"]},"column":{"s":2,"e":57}},"dim":["","list.158","listItem.1","paragraph.0","text.0"],"code":"[x] Double-touch changed from 2-finger simultaneous to "},{"id":"/root/children/158/children/1/children/0/children/1","type":"strong","loc":{"start":44457,"end":44471,"line":{"s":825,"e":825,"code":["- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"]},"column":{"s":57,"e":71}},"dim":["","list.158","listItem.1","paragraph.0","strong.1"],"code":"**double-tap**"},{"id":"/root/children/158/children/1/children/0/children/1/children/0","type":"text","loc":{"start":44459,"end":44469,"line":{"s":825,"e":825,"code":["- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"]},"column":{"s":59,"e":69}},"dim":["","list.158","listItem.1","paragraph.0","strong.1","text.0"],"code":"double-tap"},{"id":"/root/children/158/children/1/children/0/children/2","type":"text","loc":{"start":44471,"end":44495,"line":{"s":825,"e":825,"code":["- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)"]},"column":{"s":71,"e":95}},"dim":["","list.158","listItem.1","paragraph.0","text.2"],"code":" pattern (400ms timeout)"},{"id":"/root/children/158/children/2","type":"listItem","loc":{"start":44496,"end":44571,"line":{"s":826,"e":826,"code":["- [x] Native click delegation instead of Alpine @click (avoids double-fire)"]},"column":{"s":0,"e":75}},"dim":["","list.158","listItem.2"],"code":"- [x] Native click delegation instead of Alpine @click (avoids double-fire)"},{"id":"/root/children/158/children/2/children/0","type":"paragraph","loc":{"start":44498,"end":44571,"line":{"s":826,"e":826,"code":["- [x] Native click delegation instead of Alpine @click (avoids double-fire)"]},"column":{"s":2,"e":75}},"dim":["","list.158","listItem.2","paragraph.0"],"code":"[x] Native click delegation instead of Alpine @click (avoids double-fire)"},{"id":"/root/children/158/children/2/children/0/children/0","type":"text","loc":{"start":44498,"end":44571,"line":{"s":826,"e":826,"code":["- [x] Native click delegation instead of Alpine @click (avoids double-fire)"]},"column":{"s":2,"e":75}},"dim":["","list.158","listItem.2","paragraph.0","text.0"],"code":"[x] Native click delegation instead of Alpine @click (avoids double-fire)"},{"id":"/root/children/158/children/3","type":"listItem","loc":{"start":44572,"end":44648,"line":{"s":827,"e":827,"code":["- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`"]},"column":{"s":0,"e":76}},"dim":["","list.158","listItem.3"],"code":"- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`"},{"id":"/root/children/158/children/3/children/0","type":"paragraph","loc":{"start":44574,"end":44648,"line":{"s":827,"e":827,"code":["- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`"]},"column":{"s":2,"e":76}},"dim":["","list.158","listItem.3","paragraph.0"],"code":"[x] Toolbar buttons: click = primary action, double-touch = `action_extra`"},{"id":"/root/children/158/children/3/children/0/children/0","type":"text","loc":{"start":44574,"end":44634,"line":{"s":827,"e":827,"code":["- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`"]},"column":{"s":2,"e":62}},"dim":["","list.158","listItem.3","paragraph.0","text.0"],"code":"[x] Toolbar buttons: click = primary action, double-touch = "},{"id":"/root/children/158/children/3/children/0/children/1","type":"inlineCode","loc":{"start":44634,"end":44648,"line":{"s":827,"e":827,"code":["- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`"]},"column":{"s":62,"e":76}},"dim":["","list.158","listItem.3","paragraph.0","inlineCode.1"],"code":"`action_extra`"},{"id":"/root/children/158/children/4","type":"listItem","loc":{"start":44649,"end":44719,"line":{"s":828,"e":828,"code":["- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)"]},"column":{"s":0,"e":70}},"dim":["","list.158","listItem.4"],"code":"- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)"},{"id":"/root/children/158/children/4/children/0","type":"paragraph","loc":{"start":44651,"end":44719,"line":{"s":828,"e":828,"code":["- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)"]},"column":{"s":2,"e":70}},"dim":["","list.158","listItem.4","paragraph.0"],"code":"[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)"},{"id":"/root/children/158/children/4/children/0/children/0","type":"text","loc":{"start":44651,"end":44681,"line":{"s":828,"e":828,"code":["- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)"]},"column":{"s":2,"e":32}},"dim":["","list.158","listItem.4","paragraph.0","text.0"],"code":"[x] Mobile paste: uses native "},{"id":"/root/children/158/children/4/children/0/children/1","type":"inlineCode","loc":{"start":44681,"end":44691,"line":{"s":828,"e":828,"code":["- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)"]},"column":{"s":32,"e":42}},"dim":["","list.158","listItem.4","paragraph.0","inlineCode.1"],"code":"`prompt()`"},{"id":"/root/children/158/children/4/children/0/children/2","type":"text","loc":{"start":44691,"end":44719,"line":{"s":828,"e":828,"code":["- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)"]},"column":{"s":42,"e":70}},"dim":["","list.158","listItem.4","paragraph.0","text.2"],"code":" (no nice dialog, but works)"},{"id":"/root/children/158/children/5","type":"listItem","loc":{"start":44720,"end":44797,"line":{"s":829,"e":829,"code":["- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"]},"column":{"s":0,"e":77}},"dim":["","list.158","listItem.5"],"code":"- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"},{"id":"/root/children/158/children/5/children/0","type":"paragraph","loc":{"start":44722,"end":44797,"line":{"s":829,"e":829,"code":["- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"]},"column":{"s":2,"e":77}},"dim":["","list.158","listItem.5","paragraph.0"],"code":"[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"},{"id":"/root/children/158/children/5/children/0/children/0","type":"text","loc":{"start":44722,"end":44751,"line":{"s":829,"e":829,"code":["- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"]},"column":{"s":2,"e":31}},"dim":["","list.158","listItem.5","paragraph.0","text.0"],"code":"[x] Fragment touchbar: added "},{"id":"/root/children/158/children/5/children/0/children/1","type":"inlineCode","loc":{"start":44751,"end":44763,"line":{"s":829,"e":829,"code":["- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"]},"column":{"s":31,"e":43}},"dim":["","list.158","listItem.5","paragraph.0","inlineCode.1"],"code":"`touchstart`"},{"id":"/root/children/158/children/5/children/0/children/2","type":"text","loc":{"start":44763,"end":44776,"line":{"s":829,"e":829,"code":["- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"]},"column":{"s":43,"e":56}},"dim":["","list.158","listItem.5","paragraph.0","text.2"],"code":" binding for "},{"id":"/root/children/158/children/5/children/0/children/3","type":"inlineCode","loc":{"start":44776,"end":44779,"line":{"s":829,"e":829,"code":["- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"]},"column":{"s":56,"e":59}},"dim":["","list.158","listItem.5","paragraph.0","inlineCode.3"],"code":"`$`"},{"id":"/root/children/158/children/5/children/0/children/4","type":"text","loc":{"start":44779,"end":44797,"line":{"s":829,"e":829,"code":["- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips"]},"column":{"s":59,"e":77}},"dim":["","list.158","listItem.5","paragraph.0","text.4"],"code":"-prefixed tooltips"},{"id":"/root/children/158/children/6","type":"listItem","loc":{"start":44798,"end":44863,"line":{"s":830,"e":830,"code":["- [x] `data-doubletouch` only rendered when `action_extra` exists"]},"column":{"s":0,"e":65}},"dim":["","list.158","listItem.6"],"code":"- [x] `data-doubletouch` only rendered when `action_extra` exists"},{"id":"/root/children/158/children/6/children/0","type":"paragraph","loc":{"start":44800,"end":44863,"line":{"s":830,"e":830,"code":["- [x] `data-doubletouch` only rendered when `action_extra` exists"]},"column":{"s":2,"e":65}},"dim":["","list.158","listItem.6","paragraph.0"],"code":"[x] `data-doubletouch` only rendered when `action_extra` exists"},{"id":"/root/children/158/children/6/children/0/children/0","type":"text","loc":{"start":44800,"end":44804,"line":{"s":830,"e":830,"code":["- [x] `data-doubletouch` only rendered when `action_extra` exists"]},"column":{"s":2,"e":6}},"dim":["","list.158","listItem.6","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/158/children/6/children/0/children/1","type":"inlineCode","loc":{"start":44804,"end":44822,"line":{"s":830,"e":830,"code":["- [x] `data-doubletouch` only rendered when `action_extra` exists"]},"column":{"s":6,"e":24}},"dim":["","list.158","listItem.6","paragraph.0","inlineCode.1"],"code":"`data-doubletouch`"},{"id":"/root/children/158/children/6/children/0/children/2","type":"text","loc":{"start":44822,"end":44842,"line":{"s":830,"e":830,"code":["- [x] `data-doubletouch` only rendered when `action_extra` exists"]},"column":{"s":24,"e":44}},"dim":["","list.158","listItem.6","paragraph.0","text.2"],"code":" only rendered when "},{"id":"/root/children/158/children/6/children/0/children/3","type":"inlineCode","loc":{"start":44842,"end":44856,"line":{"s":830,"e":830,"code":["- [x] `data-doubletouch` only rendered when `action_extra` exists"]},"column":{"s":44,"e":58}},"dim":["","list.158","listItem.6","paragraph.0","inlineCode.3"],"code":"`action_extra`"},{"id":"/root/children/158/children/6/children/0/children/4","type":"text","loc":{"start":44856,"end":44863,"line":{"s":830,"e":830,"code":["- [x] `data-doubletouch` only rendered when `action_extra` exists"]},"column":{"s":58,"e":65}},"dim":["","list.158","listItem.6","paragraph.0","text.4"],"code":" exists"},{"id":"/root/children/158/children/7","type":"listItem","loc":{"start":44864,"end":44945,"line":{"s":831,"e":831,"code":["- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)"]},"column":{"s":0,"e":81}},"dim":["","list.158","listItem.7"],"code":"- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)"},{"id":"/root/children/158/children/7/children/0","type":"paragraph","loc":{"start":44866,"end":44945,"line":{"s":831,"e":831,"code":["- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)"]},"column":{"s":2,"e":81}},"dim":["","list.158","listItem.7","paragraph.0"],"code":"[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)"},{"id":"/root/children/158/children/7/children/0/children/0","type":"text","loc":{"start":44866,"end":44876,"line":{"s":831,"e":831,"code":["- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)"]},"column":{"s":2,"e":12}},"dim":["","list.158","listItem.7","paragraph.0","text.0"],"code":"[x] Fixed "},{"id":"/root/children/158/children/7/children/0/children/1","type":"inlineCode","loc":{"start":44876,"end":44901,"line":{"s":831,"e":831,"code":["- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)"]},"column":{"s":12,"e":37}},"dim":["","list.158","listItem.7","paragraph.0","inlineCode.1"],"code":"`window.touchbarDispatch`"},{"id":"/root/children/158/children/7/children/0/children/2","type":"text","loc":{"start":44901,"end":44945,"line":{"s":831,"e":831,"code":["- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)"]},"column":{"s":37,"e":81}},"dim":["","list.158","listItem.7","paragraph.0","text.2"],"code":" to receive origin element (not CustomEvent)"},{"id":"/root/children/158/children/8","type":"listItem","loc":{"start":44946,"end":45105,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":0,"e":159}},"dim":["","list.158","listItem.8"],"code":"- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"},{"id":"/root/children/158/children/8/children/0","type":"paragraph","loc":{"start":44948,"end":45105,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":2,"e":159}},"dim":["","list.158","listItem.8","paragraph.0"],"code":"[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"},{"id":"/root/children/158/children/8/children/0/children/0","type":"text","loc":{"start":44948,"end":44969,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":2,"e":23}},"dim":["","list.158","listItem.8","paragraph.0","text.0"],"code":"[x] Mobile hide bug: "},{"id":"/root/children/158/children/8/children/0/children/1","type":"inlineCode","loc":{"start":44969,"end":44981,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":23,"e":35}},"dim":["","list.158","listItem.8","paragraph.0","inlineCode.1"],"code":"`touchstart`"},{"id":"/root/children/158/children/8/children/0/children/2","type":"text","loc":{"start":44981,"end":45001,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":35,"e":55}},"dim":["","list.158","listItem.8","paragraph.0","text.2"],"code":" hide path now sets "},{"id":"/root/children/158/children/8/children/0/children/3","type":"inlineCode","loc":{"start":45001,"end":45019,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":55,"e":73}},"dim":["","list.158","listItem.8","paragraph.0","inlineCode.3"],"code":"`toolbarJustShown`"},{"id":"/root/children/158/children/8/children/0/children/4","type":"text","loc":{"start":45019,"end":45037,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":73,"e":91}},"dim":["","list.158","listItem.8","paragraph.0","text.4"],"code":" so the synthetic "},{"id":"/root/children/158/children/8/children/0/children/5","type":"inlineCode","loc":{"start":45037,"end":45044,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":91,"e":98}},"dim":["","list.158","listItem.8","paragraph.0","inlineCode.5"],"code":"`click`"},{"id":"/root/children/158/children/8/children/0/children/6","type":"text","loc":{"start":45044,"end":45105,"line":{"s":832,"e":832,"code":["- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"]},"column":{"s":98,"e":159}},"dim":["","list.158","listItem.8","paragraph.0","text.6"],"code":" ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)"},{"id":"/root/children/158/children/9","type":"listItem","loc":{"start":45106,"end":45247,"line":{"s":833,"e":833,"code":["- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"]},"column":{"s":0,"e":141}},"dim":["","list.158","listItem.9"],"code":"- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"},{"id":"/root/children/158/children/9/children/0","type":"paragraph","loc":{"start":45108,"end":45247,"line":{"s":833,"e":833,"code":["- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"]},"column":{"s":2,"e":141}},"dim":["","list.158","listItem.9","paragraph.0"],"code":"[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"},{"id":"/root/children/158/children/9/children/0/children/0","type":"text","loc":{"start":45108,"end":45140,"line":{"s":833,"e":833,"code":["- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"]},"column":{"s":2,"e":34}},"dim":["","list.158","listItem.9","paragraph.0","text.0"],"code":"[x] Auto-close on action click: "},{"id":"/root/children/158/children/9/children/0/children/1","type":"inlineCode","loc":{"start":45140,"end":45165,"line":{"s":833,"e":833,"code":["- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"]},"column":{"s":34,"e":59}},"dim":["","list.158","listItem.9","paragraph.0","inlineCode.1"],"code":"`window.touchbarDispatch`"},{"id":"/root/children/158/children/9/children/0/children/2","type":"text","loc":{"start":45165,"end":45172,"line":{"s":833,"e":833,"code":["- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"]},"column":{"s":59,"e":66}},"dim":["","list.158","listItem.9","paragraph.0","text.2"],"code":" calls "},{"id":"/root/children/158/children/9/children/0/children/3","type":"inlineCode","loc":{"start":45172,"end":45182,"line":{"s":833,"e":833,"code":["- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"]},"column":{"s":66,"e":76}},"dim":["","list.158","listItem.9","paragraph.0","inlineCode.3"],"code":"`fnHide()`"},{"id":"/root/children/158/children/9/children/0/children/4","type":"text","loc":{"start":45182,"end":45247,"line":{"s":833,"e":833,"code":["- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar"]},"column":{"s":76,"e":141}},"dim":["","list.158","listItem.9","paragraph.0","text.4"],"code":" after the switch so any toolbar button click closes the touchbar"},{"id":"/root/children/158/children/10","type":"listItem","loc":{"start":45248,"end":45890,"line":{"s":834,"e":837,"code":["- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:","  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)","  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown","  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":0,"e":160}},"dim":["","list.158","listItem.10"],"code":"- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"},{"id":"/root/children/158/children/10/children/0","type":"paragraph","loc":{"start":45250,"end":45315,"line":{"s":834,"e":834,"code":["- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:"]},"column":{"s":2,"e":67}},"dim":["","list.158","listItem.10","paragraph.0"],"code":"[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:"},{"id":"/root/children/158/children/10/children/0/children/0","type":"text","loc":{"start":45250,"end":45262,"line":{"s":834,"e":834,"code":["- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:"]},"column":{"s":2,"e":14}},"dim":["","list.158","listItem.10","paragraph.0","text.0"],"code":"[x] Per-def "},{"id":"/root/children/158/children/10/children/0/children/1","type":"inlineCode","loc":{"start":45262,"end":45273,"line":{"s":834,"e":834,"code":["- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:"]},"column":{"s":14,"e":25}},"dim":["","list.158","listItem.10","paragraph.0","inlineCode.1"],"code":"`hideAfter`"},{"id":"/root/children/158/children/10/children/0/children/2","type":"text","loc":{"start":45273,"end":45293,"line":{"s":834,"e":834,"code":["- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:"]},"column":{"s":25,"e":45}},"dim":["","list.158","listItem.10","paragraph.0","text.2"],"code":" opt-in debounce in "},{"id":"/root/children/158/children/10/children/0/children/3","type":"inlineCode","loc":{"start":45293,"end":45314,"line":{"s":834,"e":834,"code":["- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:"]},"column":{"s":45,"e":66}},"dim":["","list.158","listItem.10","paragraph.0","inlineCode.3"],"code":"`ui-touchbar-defs.js`"},{"id":"/root/children/158/children/10/children/0/children/4","type":"text","loc":{"start":45314,"end":45315,"line":{"s":834,"e":834,"code":["- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:"]},"column":{"s":66,"e":67}},"dim":["","list.158","listItem.10","paragraph.0","text.4"],"code":":"},{"id":"/root/children/158/children/10/children/1","type":"list","loc":{"start":45318,"end":45890,"line":{"s":835,"e":837,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)","  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown","  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":2,"e":160}},"dim":["","list.158","listItem.10","list.1"],"code":"- omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`","symbName":"list","symbRange":[null,null],"symbRangeL":[835,837],"outerCode":"  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown","outerHtml":"<ul><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li></ul>"},{"id":"/root/children/158/children/10/children/1/children/0","type":"listItem","loc":{"start":45318,"end":45492,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":2,"e":176}},"dim":["","list.158","listItem.10","list.1","listItem.0"],"code":"- omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"},{"id":"/root/children/158/children/10/children/1/children/0/children/0","type":"paragraph","loc":{"start":45320,"end":45492,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":4,"e":176}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0"],"code":"omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/0","type":"text","loc":{"start":45320,"end":45330,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":4,"e":14}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","text.0"],"code":"omitted / "},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/1","type":"inlineCode","loc":{"start":45330,"end":45337,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":14,"e":21}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","inlineCode.1"],"code":"`false`"},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/2","type":"text","loc":{"start":45337,"end":45364,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":21,"e":48}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","text.2"],"code":" / non-numeric (default) → "},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/3","type":"strong","loc":{"start":45364,"end":45377,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":48,"e":61}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","strong.3"],"code":"**keep open**"},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/3/children/0","type":"text","loc":{"start":45366,"end":45375,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":50,"e":59}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","strong.3","text.0"],"code":"keep open"},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/4","type":"text","loc":{"start":45377,"end":45460,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":61,"e":144}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","text.4"],"code":" (touchbar stays visible after action; user dismisses via re-tap on trigger or via "},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/5","type":"inlineCode","loc":{"start":45460,"end":45469,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":144,"e":153}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","inlineCode.5"],"code":"`glass:*`"},{"id":"/root/children/158/children/10/children/1/children/0/children/0/children/6","type":"text","loc":{"start":45469,"end":45492,"line":{"s":835,"e":835,"code":["  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)"]},"column":{"s":153,"e":176}},"dim":["","list.158","listItem.10","list.1","listItem.0","paragraph.0","text.6"],"code":" explicit-hide actions)"},{"id":"/root/children/158/children/10/children/1/children/1","type":"listItem","loc":{"start":45495,"end":45729,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":2,"e":236}},"dim":["","list.158","listItem.10","list.1","listItem.1"],"code":"- `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"},{"id":"/root/children/158/children/10/children/1/children/1/children/0","type":"paragraph","loc":{"start":45497,"end":45729,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":4,"e":236}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0"],"code":"`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/0","type":"inlineCode","loc":{"start":45497,"end":45514,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":4,"e":21}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.0"],"code":"`hideAfter: <ms>`"},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/1","type":"text","loc":{"start":45514,"end":45553,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":21,"e":60}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","text.1"],"code":" (number or numeric string) → schedule "},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/2","type":"inlineCode","loc":{"start":45553,"end":45561,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":60,"e":68}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.2"],"code":"`fnHide`"},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/3","type":"text","loc":{"start":45561,"end":45574,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":68,"e":81}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","text.3"],"code":" N ms later; "},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/4","type":"strong","loc":{"start":45574,"end":45600,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":81,"e":107}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","strong.4"],"code":"**global last-click-wins**"},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/4/children/0","type":"text","loc":{"start":45576,"end":45598,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":83,"e":105}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","strong.4","text.0"],"code":"global last-click-wins"},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/5","type":"text","loc":{"start":45600,"end":45657,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":107,"e":164}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","text.5"],"code":" — every dispatched click clears the shared module-level "},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/6","type":"inlineCode","loc":{"start":45657,"end":45668,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":164,"e":175}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.6"],"code":"`hideTimer`"},{"id":"/root/children/158/children/10/children/1/children/1/children/0/children/7","type":"text","loc":{"start":45668,"end":45729,"line":{"s":836,"e":836,"code":["  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown"]},"column":{"s":175,"e":236}},"dim":["","list.158","listItem.10","list.1","listItem.1","paragraph.0","text.7"],"code":" first, so rapid clicks (e.g. multi-vote) reset the countdown"},{"id":"/root/children/158/children/10/children/1/children/2","type":"listItem","loc":{"start":45732,"end":45890,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":2,"e":160}},"dim":["","list.158","listItem.10","list.1","listItem.2"],"code":"- Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"},{"id":"/root/children/158/children/10/children/1/children/2/children/0","type":"paragraph","loc":{"start":45734,"end":45890,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":4,"e":160}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0"],"code":"Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/0","type":"text","loc":{"start":45734,"end":45752,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":4,"e":22}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","text.0"],"code":"Wired by emitting "},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/1","type":"inlineCode","loc":{"start":45752,"end":45775,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":22,"e":45}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","inlineCode.1"],"code":"`data-hide-after=\"...\"`"},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/2","type":"text","loc":{"start":45775,"end":45794,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":45,"e":64}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","text.2"],"code":" on each button in "},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/3","type":"inlineCode","loc":{"start":45794,"end":45814,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":64,"e":84}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","inlineCode.3"],"code":"`updateToolbarItems`"},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/4","type":"text","loc":{"start":45814,"end":45827,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":84,"e":97}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","text.4"],"code":" and reading "},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/5","type":"inlineCode","loc":{"start":45827,"end":45868,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":97,"e":138}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","inlineCode.5"],"code":"`element.getAttribute(\"data-hide-after\")`"},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/6","type":"text","loc":{"start":45868,"end":45872,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":138,"e":142}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","text.6"],"code":" in "},{"id":"/root/children/158/children/10/children/1/children/2/children/0/children/7","type":"inlineCode","loc":{"start":45872,"end":45890,"line":{"s":837,"e":837,"code":["  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`"]},"column":{"s":142,"e":160}},"dim":["","list.158","listItem.10","list.1","listItem.2","paragraph.0","inlineCode.7"],"code":"`touchbarDispatch`"},{"id":"/root/children/158/children/11","type":"listItem","loc":{"start":45891,"end":46326,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":0,"e":435}},"dim":["","list.158","listItem.11"],"code":"- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."},{"id":"/root/children/158/children/11/children/0","type":"paragraph","loc":{"start":45893,"end":46326,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":2,"e":435}},"dim":["","list.158","listItem.11","paragraph.0"],"code":"[x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."},{"id":"/root/children/158/children/11/children/0/children/0","type":"text","loc":{"start":45893,"end":45897,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":2,"e":6}},"dim":["","list.158","listItem.11","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/158/children/11/children/0/children/1","type":"strong","loc":{"start":45897,"end":45944,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":6,"e":53}},"dim":["","list.158","listItem.11","paragraph.0","strong.1"],"code":"**`window.touchbarShow(definition, position)`**"},{"id":"/root/children/158/children/11/children/0/children/1/children/0","type":"inlineCode","loc":{"start":45899,"end":45942,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":8,"e":51}},"dim":["","list.158","listItem.11","paragraph.0","strong.1","inlineCode.0"],"code":"`window.touchbarShow(definition, position)`"},{"id":"/root/children/158/children/11/children/0/children/2","type":"text","loc":{"start":45944,"end":45990,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":53,"e":99}},"dim":["","list.158","listItem.11","paragraph.0","text.2"],"code":" — programmatic touchbar invocation without a "},{"id":"/root/children/158/children/11/children/0/children/3","type":"inlineCode","loc":{"start":45990,"end":46006,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":99,"e":115}},"dim":["","list.158","listItem.11","paragraph.0","inlineCode.3"],"code":"`[data-tooltip]`"},{"id":"/root/children/158/children/11/children/0/children/4","type":"text","loc":{"start":46006,"end":46054,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":115,"e":163}},"dim":["","list.158","listItem.11","paragraph.0","text.4"],"code":" host element. Creates a synthetic element with "},{"id":"/root/children/158/children/11/children/0/children/5","type":"inlineCode","loc":{"start":46054,"end":46058,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":163,"e":167}},"dim":["","list.158","listItem.11","paragraph.0","inlineCode.5"],"code":"`id`"},{"id":"/root/children/158/children/11/children/0/children/6","type":"text","loc":{"start":46058,"end":46061,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":167,"e":170}},"dim":["","list.158","listItem.11","paragraph.0","text.6"],"code":" + "},{"id":"/root/children/158/children/11/children/0/children/7","type":"inlineCode","loc":{"start":46061,"end":46075,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":170,"e":184}},"dim":["","list.158","listItem.11","paragraph.0","inlineCode.7"],"code":"`data-tooltip`"},{"id":"/root/children/158/children/11/children/0/children/8","type":"text","loc":{"start":46075,"end":46083,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":184,"e":192}},"dim":["","list.158","listItem.11","paragraph.0","text.8"],"code":", calls "},{"id":"/root/children/158/children/11/children/0/children/9","type":"inlineCode","loc":{"start":46083,"end":46104,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":192,"e":213}},"dim":["","list.158","listItem.11","paragraph.0","inlineCode.9"],"code":"`tooltipMaker.render`"},{"id":"/root/children/158/children/11/children/0/children/10","type":"text","loc":{"start":46104,"end":46129,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":213,"e":238}},"dim":["","list.158","listItem.11","paragraph.0","text.10"],"code":" directly, positions via "},{"id":"/root/children/158/children/11/children/0/children/11","type":"inlineCode","loc":{"start":46129,"end":46146,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":238,"e":255}},"dim":["","list.158","listItem.11","paragraph.0","inlineCode.11"],"code":"`positionTooltip`"},{"id":"/root/children/158/children/11/children/0/children/12","type":"text","loc":{"start":46146,"end":46209,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":255,"e":318}},"dim":["","list.158","listItem.11","paragraph.0","text.12"],"code":" with a 0×0 emitter rect at the given viewport coords. Used by "},{"id":"/root/children/158/children/11/children/0/children/13","type":"inlineCode","loc":{"start":46209,"end":46231,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":318,"e":340}},"dim":["","list.158","listItem.11","paragraph.0","inlineCode.13"],"code":"`cmdVotingCampaign.js`"},{"id":"/root/children/158/children/11/children/0/children/14","type":"text","loc":{"start":46231,"end":46239,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":340,"e":348}},"dim":["","list.158","listItem.11","paragraph.0","text.14"],"code":"'s Vega "},{"id":"/root/children/158/children/11/children/0/children/15","type":"inlineCode","loc":{"start":46239,"end":46252,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":348,"e":361}},"dim":["","list.158","listItem.11","paragraph.0","inlineCode.15"],"code":"`clickedWord`"},{"id":"/root/children/158/children/11/children/0/children/16","type":"text","loc":{"start":46252,"end":46326,"line":{"s":838,"e":838,"code":["- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position."]},"column":{"s":361,"e":435}},"dim":["","list.158","listItem.11","paragraph.0","text.16"],"code":" listener to open the fragment-action menu at the clicked task's position."},{"id":"/root/children/159","type":"heading","loc":{"start":46328,"end":46341,"line":{"s":840,"e":840,"code":["## Known gaps"]},"column":{"s":0,"e":13}},"dim":["","heading.159"],"code":"## Known gaps","symbName":"heading","symbRange":[46343,46714],"symbRangeL":[840,844],"outerCode":"\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check","outerHtml":"\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>"},{"id":"/root/children/159/children/0","type":"text","loc":{"start":46331,"end":46341,"line":{"s":840,"e":840,"code":["## Known gaps"]},"column":{"s":3,"e":13}},"dim":["","heading.159","text.0"],"code":"Known gaps"},{"id":"/root/children/160","type":"list","loc":{"start":46343,"end":46714,"line":{"s":842,"e":843,"code":["- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)","- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":0,"e":211}},"dim":["","list.160"],"code":"- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check","symbName":"list","symbRange":[46716,46733],"symbRangeL":[842,848],"outerCode":"- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done","outerHtml":"<ul><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>"},{"id":"/root/children/160/children/0","type":"listItem","loc":{"start":46343,"end":46502,"line":{"s":842,"e":842,"code":["- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)"]},"column":{"s":0,"e":159}},"dim":["","list.160","listItem.0"],"code":"- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)"},{"id":"/root/children/160/children/0/children/0","type":"paragraph","loc":{"start":46345,"end":46502,"line":{"s":842,"e":842,"code":["- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)"]},"column":{"s":2,"e":159}},"dim":["","list.160","listItem.0","paragraph.0"],"code":"No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)"},{"id":"/root/children/160/children/0/children/0/children/0","type":"text","loc":{"start":46345,"end":46428,"line":{"s":842,"e":842,"code":["- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)"]},"column":{"s":2,"e":85}},"dim":["","list.160","listItem.0","paragraph.0","text.0"],"code":"No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific "},{"id":"/root/children/160/children/0/children/0/children/1","type":"inlineCode","loc":{"start":46428,"end":46446,"line":{"s":842,"e":842,"code":["- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)"]},"column":{"s":85,"e":103}},"dim":["","list.160","listItem.0","paragraph.0","inlineCode.1"],"code":"`touchbarDispatch`"},{"id":"/root/children/160/children/0/children/0/children/2","type":"text","loc":{"start":46446,"end":46502,"line":{"s":842,"e":842,"code":["- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)"]},"column":{"s":103,"e":159}},"dim":["","list.160","listItem.0","paragraph.0","text.2"],"code":" verbs (glass:clear, glass:clear-history, glass:history)"},{"id":"/root/children/160/children/1","type":"listItem","loc":{"start":46503,"end":46714,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":0,"e":211}},"dim":["","list.160","listItem.1"],"code":"- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"},{"id":"/root/children/160/children/1/children/0","type":"paragraph","loc":{"start":46505,"end":46714,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":2,"e":211}},"dim":["","list.160","listItem.1","paragraph.0"],"code":"`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"},{"id":"/root/children/160/children/1/children/0/children/0","type":"inlineCode","loc":{"start":46505,"end":46544,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":2,"e":41}},"dim":["","list.160","listItem.1","paragraph.0","inlineCode.0"],"code":"`initTouchpad(\"#alpine-touchpad\", ...)`"},{"id":"/root/children/160/children/1/children/0/children/1","type":"text","loc":{"start":46544,"end":46622,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":41,"e":119}},"dim":["","list.160","listItem.1","paragraph.0","text.1"],"code":" in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has "},{"id":"/root/children/160/children/1/children/0/children/2","type":"inlineCode","loc":{"start":46622,"end":46640,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":119,"e":137}},"dim":["","list.160","listItem.1","paragraph.0","inlineCode.2"],"code":"`#alpine-touchbar`"},{"id":"/root/children/160/children/1/children/0/children/3","type":"text","loc":{"start":46640,"end":46643,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":137,"e":140}},"dim":["","list.160","listItem.1","paragraph.0","text.3"],"code":"); "},{"id":"/root/children/160/children/1/children/0/children/4","type":"inlineCode","loc":{"start":46643,"end":46660,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":140,"e":157}},"dim":["","list.160","listItem.1","paragraph.0","inlineCode.4"],"code":"`initDoubleTouch`"},{"id":"/root/children/160/children/1/children/0/children/5","type":"text","loc":{"start":46660,"end":46714,"line":{"s":843,"e":843,"code":["- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check"]},"column":{"s":157,"e":211}},"dim":["","list.160","listItem.1","paragraph.0","text.5"],"code":" still runs because it fires before the selector check"},{"id":"/root/children/161","type":"heading","loc":{"start":46716,"end":46724,"line":{"s":845,"e":845,"code":["# Tabler"]},"column":{"s":0,"e":8}},"dim":["","heading.161"],"code":"# Tabler","symbName":"heading","symbRange":[46726,70981],"symbRangeL":[845,846],"outerCode":"","outerHtml":""},{"id":"/root/children/161/children/0","type":"text","loc":{"start":46718,"end":46724,"line":{"s":845,"e":845,"code":["# Tabler"]},"column":{"s":2,"e":8}},"dim":["","heading.161","text.0"],"code":"Tabler"},{"id":"/root/children/162","type":"heading","loc":{"start":46726,"end":46733,"line":{"s":847,"e":847,"code":["## Done"]},"column":{"s":0,"e":7}},"dim":["","heading.162"],"code":"## Done","symbName":"heading","symbRange":[46735,47878],"symbRangeL":[847,854],"outerCode":"\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order","outerHtml":"\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>"},{"id":"/root/children/162/children/0","type":"text","loc":{"start":46729,"end":46733,"line":{"s":847,"e":847,"code":["## Done"]},"column":{"s":3,"e":7}},"dim":["","heading.162","text.0"],"code":"Done"},{"id":"/root/children/163","type":"list","loc":{"start":46735,"end":47878,"line":{"s":849,"e":853,"code":["- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)","- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)","- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup","- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast","- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":0,"e":249}},"dim":["","list.163"],"code":"- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order","symbName":"list","symbRange":[47880,47908],"symbRangeL":[849,858],"outerCode":"- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done","outerHtml":"<ul><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>"},{"id":"/root/children/163/children/0","type":"listItem","loc":{"start":46735,"end":46837,"line":{"s":849,"e":849,"code":["- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"]},"column":{"s":0,"e":102}},"dim":["","list.163","listItem.0"],"code":"- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"},{"id":"/root/children/163/children/0/children/0","type":"paragraph","loc":{"start":46737,"end":46837,"line":{"s":849,"e":849,"code":["- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"]},"column":{"s":2,"e":102}},"dim":["","list.163","listItem.0","paragraph.0"],"code":"[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"},{"id":"/root/children/163/children/0/children/0/children/0","type":"text","loc":{"start":46737,"end":46741,"line":{"s":849,"e":849,"code":["- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"]},"column":{"s":2,"e":6}},"dim":["","list.163","listItem.0","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/163/children/0/children/0/children/1","type":"inlineCode","loc":{"start":46741,"end":46759,"line":{"s":849,"e":849,"code":["- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"]},"column":{"s":6,"e":24}},"dim":["","list.163","listItem.0","paragraph.0","inlineCode.1"],"code":"`player/tabler.js`"},{"id":"/root/children/163/children/0/children/0/children/2","type":"text","loc":{"start":46759,"end":46837,"line":{"s":849,"e":849,"code":["- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"]},"column":{"s":24,"e":102}},"dim":["","list.163","listItem.0","paragraph.0","text.2"],"code":" now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)"},{"id":"/root/children/163/children/1","type":"listItem","loc":{"start":46838,"end":47109,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":0,"e":271}},"dim":["","list.163","listItem.1"],"code":"- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"},{"id":"/root/children/163/children/1/children/0","type":"paragraph","loc":{"start":46840,"end":47109,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":2,"e":271}},"dim":["","list.163","listItem.1","paragraph.0"],"code":"[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"},{"id":"/root/children/163/children/1/children/0/children/0","type":"text","loc":{"start":46840,"end":46844,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":2,"e":6}},"dim":["","list.163","listItem.1","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/163/children/1/children/0/children/1","type":"inlineCode","loc":{"start":46844,"end":46871,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":6,"e":33}},"dim":["","list.163","listItem.1","paragraph.0","inlineCode.1"],"code":"`div.tabulator-tableholder`"},{"id":"/root/children/163/children/1/children/0/children/2","type":"text","loc":{"start":46871,"end":46912,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":33,"e":74}},"dim":["","list.163","listItem.1","paragraph.0","text.2"],"code":" auto-shrinks for short tables: requires "},{"id":"/root/children/163/children/1/children/0/children/3","type":"strong","loc":{"start":46912,"end":46920,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":74,"e":82}},"dim":["","list.163","listItem.1","paragraph.0","strong.3"],"code":"**both**"},{"id":"/root/children/163/children/1/children/0/children/3/children/0","type":"text","loc":{"start":46914,"end":46918,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":76,"e":80}},"dim":["","list.163","listItem.1","paragraph.0","strong.3","text.0"],"code":"both"},{"id":"/root/children/163/children/1/children/0/children/4","type":"text","loc":{"start":46920,"end":46921,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":82,"e":83}},"dim":["","list.163","listItem.1","paragraph.0","text.4"],"code":" "},{"id":"/root/children/163/children/1/children/0/children/5","type":"inlineCode","loc":{"start":46921,"end":46936,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":83,"e":98}},"dim":["","list.163","listItem.1","paragraph.0","inlineCode.5"],"code":"`height: false`"},{"id":"/root/children/163/children/1/children/0/children/6","type":"text","loc":{"start":46936,"end":46941,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":98,"e":103}},"dim":["","list.163","listItem.1","paragraph.0","text.6"],"code":" and "},{"id":"/root/children/163/children/1/children/0/children/7","type":"inlineCode","loc":{"start":46941,"end":46960,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":103,"e":122}},"dim":["","list.163","listItem.1","paragraph.0","inlineCode.7"],"code":"`maxHeight: '90vh'`"},{"id":"/root/children/163/children/1/children/0/children/8","type":"text","loc":{"start":46960,"end":46980,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":122,"e":142}},"dim":["","list.163","listItem.1","paragraph.0","text.8"],"code":" at tabler.js:150 — "},{"id":"/root/children/163/children/1/children/0/children/9","type":"inlineCode","loc":{"start":46980,"end":46991,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":142,"e":153}},"dim":["","list.163","listItem.1","paragraph.0","inlineCode.9"],"code":"`maxHeight`"},{"id":"/root/children/163/children/1/children/0/children/10","type":"text","loc":{"start":46991,"end":47051,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":153,"e":213}},"dim":["","list.163","listItem.1","paragraph.0","text.10"],"code":" alone did not shrink (Tabulator 6 defaults to fill-parent; "},{"id":"/root/children/163/children/1/children/0/children/11","type":"inlineCode","loc":{"start":47051,"end":47066,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":213,"e":228}},"dim":["","list.163","listItem.1","paragraph.0","inlineCode.11"],"code":"`height: false`"},{"id":"/root/children/163/children/1/children/0/children/12","type":"text","loc":{"start":47066,"end":47089,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":228,"e":251}},"dim":["","list.163","listItem.1","paragraph.0","text.12"],"code":" turns on auto-sizing, "},{"id":"/root/children/163/children/1/children/0/children/13","type":"inlineCode","loc":{"start":47089,"end":47100,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":251,"e":262}},"dim":["","list.163","listItem.1","paragraph.0","inlineCode.13"],"code":"`maxHeight`"},{"id":"/root/children/163/children/1/children/0/children/14","type":"text","loc":{"start":47100,"end":47109,"line":{"s":850,"e":850,"code":["- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)"]},"column":{"s":262,"e":271}},"dim":["","list.163","listItem.1","paragraph.0","text.14"],"code":" caps it)"},{"id":"/root/children/163/children/2","type":"listItem","loc":{"start":47110,"end":47265,"line":{"s":851,"e":851,"code":["- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"]},"column":{"s":0,"e":155}},"dim":["","list.163","listItem.2"],"code":"- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"},{"id":"/root/children/163/children/2/children/0","type":"paragraph","loc":{"start":47112,"end":47265,"line":{"s":851,"e":851,"code":["- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"]},"column":{"s":2,"e":155}},"dim":["","list.163","listItem.2","paragraph.0"],"code":"[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"},{"id":"/root/children/163/children/2/children/0/children/0","type":"text","loc":{"start":47112,"end":47160,"line":{"s":851,"e":851,"code":["- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"]},"column":{"s":2,"e":50}},"dim":["","list.163","listItem.2","paragraph.0","text.0"],"code":"[x] HTML in cells rendered (not escaped): added "},{"id":"/root/children/163/children/2/children/0/children/1","type":"inlineCode","loc":{"start":47160,"end":47199,"line":{"s":851,"e":851,"code":["- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"]},"column":{"s":50,"e":89}},"dim":["","list.163","listItem.2","paragraph.0","inlineCode.1"],"code":"`columnDefaults: { formatter: 'html' }`"},{"id":"/root/children/163/children/2/children/0/children/2","type":"text","loc":{"start":47199,"end":47233,"line":{"s":851,"e":851,"code":["- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"]},"column":{"s":89,"e":123}},"dim":["","list.163","listItem.2","paragraph.0","text.2"],"code":" — Tabulator default formatter is "},{"id":"/root/children/163/children/2/children/0/children/3","type":"inlineCode","loc":{"start":47233,"end":47244,"line":{"s":851,"e":851,"code":["- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"]},"column":{"s":123,"e":134}},"dim":["","list.163","listItem.2","paragraph.0","inlineCode.3"],"code":"`plaintext`"},{"id":"/root/children/163/children/2/children/0/children/4","type":"text","loc":{"start":47244,"end":47265,"line":{"s":851,"e":851,"code":["- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup"]},"column":{"s":134,"e":155}},"dim":["","list.163","listItem.2","paragraph.0","text.4"],"code":" which escapes markup"},{"id":"/root/children/163/children/3","type":"listItem","loc":{"start":47266,"end":47628,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":0,"e":362}},"dim":["","list.163","listItem.3"],"code":"- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"},{"id":"/root/children/163/children/3/children/0","type":"paragraph","loc":{"start":47268,"end":47628,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":2,"e":362}},"dim":["","list.163","listItem.3","paragraph.0"],"code":"[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"},{"id":"/root/children/163/children/3/children/0/children/0","type":"text","loc":{"start":47268,"end":47296,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":2,"e":30}},"dim":["","list.163","listItem.3","paragraph.0","text.0"],"code":"[x] Themed Tabulator CSS in "},{"id":"/root/children/163/children/3/children/0/children/1","type":"inlineCode","loc":{"start":47296,"end":47336,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":30,"e":70}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.1"],"code":"`source/assets/theme-bare/repolero.less`"},{"id":"/root/children/163/children/3/children/0/children/2","type":"text","loc":{"start":47336,"end":47388,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":70,"e":122}},"dim":["","list.163","listItem.3","paragraph.0","text.2"],"code":" (appended at bottom) — uses the repolero CSS vars ("},{"id":"/root/children/163/children/3/children/0/children/3","type":"inlineCode","loc":{"start":47388,"end":47410,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":122,"e":144}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.3"],"code":"`--background-color-*`"},{"id":"/root/children/163/children/3/children/0/children/4","type":"text","loc":{"start":47410,"end":47412,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":144,"e":146}},"dim":["","list.163","listItem.3","paragraph.0","text.4"],"code":", "},{"id":"/root/children/163/children/3/children/0/children/5","type":"inlineCode","loc":{"start":47412,"end":47428,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":146,"e":162}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.5"],"code":"`--font-color-*`"},{"id":"/root/children/163/children/3/children/0/children/6","type":"text","loc":{"start":47428,"end":47430,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":162,"e":164}},"dim":["","list.163","listItem.3","paragraph.0","text.6"],"code":", "},{"id":"/root/children/163/children/3/children/0/children/7","type":"inlineCode","loc":{"start":47430,"end":47448,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":164,"e":182}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.7"],"code":"`--border-color-*`"},{"id":"/root/children/163/children/3/children/0/children/8","type":"text","loc":{"start":47448,"end":47450,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":182,"e":184}},"dim":["","list.163","listItem.3","paragraph.0","text.8"],"code":", "},{"id":"/root/children/163/children/3/children/0/children/9","type":"inlineCode","loc":{"start":47450,"end":47471,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":184,"e":205}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.9"],"code":"`--font-family-small`"},{"id":"/root/children/163/children/3/children/0/children/10","type":"text","loc":{"start":47471,"end":47473,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":205,"e":207}},"dim":["","list.163","listItem.3","paragraph.0","text.10"],"code":", "},{"id":"/root/children/163/children/3/children/0/children/11","type":"inlineCode","loc":{"start":47473,"end":47493,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":207,"e":227}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.11"],"code":"`--font-family-bold`"},{"id":"/root/children/163/children/3/children/0/children/12","type":"text","loc":{"start":47493,"end":47517,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":227,"e":251}},"dim":["","list.163","listItem.3","paragraph.0","text.12"],"code":") so dark/light follows "},{"id":"/root/children/163/children/3/children/0/children/13","type":"inlineCode","loc":{"start":47517,"end":47528,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":251,"e":262}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.13"],"code":"`body.dark`"},{"id":"/root/children/163/children/3/children/0/children/14","type":"text","loc":{"start":47528,"end":47531,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":262,"e":265}},"dim":["","list.163","listItem.3","paragraph.0","text.14"],"code":" / "},{"id":"/root/children/163/children/3/children/0/children/15","type":"inlineCode","loc":{"start":47531,"end":47543,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":265,"e":277}},"dim":["","list.163","listItem.3","paragraph.0","inlineCode.15"],"code":"`body.light`"},{"id":"/root/children/163/children/3/children/0/children/16","type":"text","loc":{"start":47543,"end":47628,"line":{"s":852,"e":852,"code":["- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast"]},"column":{"s":277,"e":362}},"dim":["","list.163","listItem.3","paragraph.0","text.16"],"code":" automatically; small mode-specific tweaks for selected-row color and border contrast"},{"id":"/root/children/163/children/4","type":"listItem","loc":{"start":47629,"end":47878,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":0,"e":249}},"dim":["","list.163","listItem.4"],"code":"- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"},{"id":"/root/children/163/children/4/children/0","type":"paragraph","loc":{"start":47631,"end":47878,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":2,"e":249}},"dim":["","list.163","listItem.4","paragraph.0"],"code":"[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"},{"id":"/root/children/163/children/4/children/0/children/0","type":"text","loc":{"start":47631,"end":47635,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":2,"e":6}},"dim":["","list.163","listItem.4","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/163/children/4/children/0/children/1","type":"inlineCode","loc":{"start":47635,"end":47651,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":6,"e":22}},"dim":["","list.163","listItem.4","paragraph.0","inlineCode.1"],"code":"`.tabulator-col`"},{"id":"/root/children/163/children/4/children/0/children/2","type":"text","loc":{"start":47651,"end":47686,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":22,"e":57}},"dim":["","list.163","listItem.4","paragraph.0","text.2"],"code":" overrides need to be nested under "},{"id":"/root/children/163/children/4/children/0/children/3","type":"inlineCode","loc":{"start":47686,"end":47705,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":57,"e":76}},"dim":["","list.163","listItem.4","paragraph.0","inlineCode.3"],"code":"`.tabulator-header`"},{"id":"/root/children/163/children/4/children/0/children/4","type":"text","loc":{"start":47705,"end":47716,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":76,"e":87}},"dim":["","list.163","listItem.4","paragraph.0","text.4"],"code":" (not just "},{"id":"/root/children/163/children/4/children/0/children/5","type":"inlineCode","loc":{"start":47716,"end":47728,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":87,"e":99}},"dim":["","list.163","listItem.4","paragraph.0","inlineCode.5"],"code":"`.tabulator`"},{"id":"/root/children/163/children/4/children/0/children/6","type":"text","loc":{"start":47728,"end":47764,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":99,"e":135}},"dim":["","list.163","listItem.4","paragraph.0","text.6"],"code":") to beat the bundled-theme default "},{"id":"/root/children/163/children/4/children/0/children/7","type":"inlineCode","loc":{"start":47764,"end":47829,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":135,"e":200}},"dim":["","list.163","listItem.4","paragraph.0","inlineCode.7"],"code":"`.tabulator .tabulator-header .tabulator-col { background:#fff }`"},{"id":"/root/children/163/children/4/children/0/children/8","type":"text","loc":{"start":47829,"end":47878,"line":{"s":853,"e":853,"code":["- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order"]},"column":{"s":200,"e":249}},"dim":["","list.163","listItem.4","paragraph.0","text.8"],"code":" — same specificity (0,3,0) wins by cascade order"},{"id":"/root/children/164","type":"heading","loc":{"start":47880,"end":47899,"line":{"s":855,"e":855,"code":["# Eisenhower matrix"]},"column":{"s":0,"e":19}},"dim":["","heading.164"],"code":"# Eisenhower matrix","symbName":"heading","symbRange":[47901,70981],"symbRangeL":[855,856],"outerCode":"","outerHtml":""},{"id":"/root/children/164/children/0","type":"text","loc":{"start":47882,"end":47899,"line":{"s":855,"e":855,"code":["# Eisenhower matrix"]},"column":{"s":2,"e":19}},"dim":["","heading.164","text.0"],"code":"Eisenhower matrix"},{"id":"/root/children/165","type":"heading","loc":{"start":47901,"end":47908,"line":{"s":857,"e":857,"code":["## Done"]},"column":{"s":0,"e":7}},"dim":["","heading.165"],"code":"## Done","symbName":"heading","symbRange":[47910,52944],"symbRangeL":[857,880],"outerCode":"\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.","outerHtml":"\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>"},{"id":"/root/children/165/children/0","type":"text","loc":{"start":47904,"end":47908,"line":{"s":857,"e":857,"code":["## Done"]},"column":{"s":3,"e":7}},"dim":["","heading.165","text.0"],"code":"Done"},{"id":"/root/children/166","type":"list","loc":{"start":47910,"end":52944,"line":{"s":859,"e":879,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.","- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.","- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.","- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.","- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.","- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.","- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.","- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.","- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.","- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.","- [x] **`showBorder` box width fix** — Two corrections:","  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.","  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).","- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:","  1. Compute `mean` and `stdDev` of task scores in quadrant","  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`","  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers","  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`","  - Controlled by `config.densityOptimum` (default `0.6`)","  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar","- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":0,"e":458}},"dim":["","list.166"],"code":"- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.","symbName":"list","symbRange":[52946,52952],"symbRangeL":[859,882],"outerCode":"- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use","outerHtml":"<ul><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>"},{"id":"/root/children/166/children/0","type":"listItem","loc":{"start":47910,"end":48460,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":0,"e":550}},"dim":["","list.166","listItem.0"],"code":"- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."},{"id":"/root/children/166/children/0/children/0","type":"paragraph","loc":{"start":47912,"end":48460,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":2,"e":550}},"dim":["","list.166","listItem.0","paragraph.0"],"code":"[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."},{"id":"/root/children/166/children/0/children/0/children/0","type":"text","loc":{"start":47912,"end":47916,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.0","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/0/children/0/children/1","type":"inlineCode","loc":{"start":47916,"end":47945,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":6,"e":35}},"dim":["","list.166","listItem.0","paragraph.0","inlineCode.1"],"code":"`player/eisenhover-matrix.js`"},{"id":"/root/children/166/children/0/children/0/children/2","type":"text","loc":{"start":47945,"end":47962,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":35,"e":52}},"dim":["","list.166","listItem.0","paragraph.0","text.2"],"code":" — replaced Vega "},{"id":"/root/children/166/children/0/children/0/children/3","type":"inlineCode","loc":{"start":47962,"end":47973,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":52,"e":63}},"dim":["","list.166","listItem.0","paragraph.0","inlineCode.3"],"code":"`wordcloud`"},{"id":"/root/children/166/children/0/children/0/children/4","type":"text","loc":{"start":47973,"end":48139,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":63,"e":229}},"dim":["","list.166","listItem.0","paragraph.0","text.4"],"code":" transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned "},{"id":"/root/children/166/children/0/children/0/children/5","type":"inlineCode","loc":{"start":48139,"end":48163,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":229,"e":253}},"dim":["","list.166","listItem.0","paragraph.0","inlineCode.5"],"code":"`{task, fontSize, x, y}`"},{"id":"/root/children/166/children/0/children/0/children/6","type":"text","loc":{"start":48163,"end":48241,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":253,"e":331}},"dim":["","list.166","listItem.0","paragraph.0","text.6"],"code":" to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum "},{"id":"/root/children/166/children/0/children/0/children/7","type":"inlineCode","loc":{"start":48241,"end":48251,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":331,"e":341}},"dim":["","list.166","listItem.0","paragraph.0","inlineCode.7"],"code":"`fontSize`"},{"id":"/root/children/166/children/0/children/0/children/8","type":"text","loc":{"start":48251,"end":48267,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":341,"e":357}},"dim":["","list.166","listItem.0","paragraph.0","text.8"],"code":" accessor (both "},{"id":"/root/children/166/children/0/children/0/children/9","type":"inlineCode","loc":{"start":48267,"end":48276,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":357,"e":366}},"dim":["","list.166","listItem.0","paragraph.0","inlineCode.9"],"code":"`{field}`"},{"id":"/root/children/166/children/0/children/0/children/10","type":"text","loc":{"start":48276,"end":48281,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":366,"e":371}},"dim":["","list.166","listItem.0","paragraph.0","text.10"],"code":" and "},{"id":"/root/children/166/children/0/children/0/children/11","type":"inlineCode","loc":{"start":48281,"end":48289,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":371,"e":379}},"dim":["","list.166","listItem.0","paragraph.0","inlineCode.11"],"code":"`{expr}`"},{"id":"/root/children/166/children/0/children/0/children/12","type":"text","loc":{"start":48289,"end":48304,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":379,"e":394}},"dim":["","list.166","listItem.0","paragraph.0","text.12"],"code":" forms) throws "},{"id":"/root/children/166/children/0/children/0/children/13","type":"inlineCode","loc":{"start":48304,"end":48359,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":394,"e":449}},"dim":["","list.166","listItem.0","paragraph.0","inlineCode.13"],"code":"`Cannot read properties of undefined (reading 'score')`"},{"id":"/root/children/166/children/0/children/0/children/14","type":"text","loc":{"start":48359,"end":48460,"line":{"s":859,"e":859,"code":["- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."]},"column":{"s":449,"e":550}},"dim":["","list.166","listItem.0","paragraph.0","text.14"],"code":"; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems."},{"id":"/root/children/166/children/1","type":"listItem","loc":{"start":48461,"end":48792,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":0,"e":331}},"dim":["","list.166","listItem.1"],"code":"- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."},{"id":"/root/children/166/children/1/children/0","type":"paragraph","loc":{"start":48463,"end":48792,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":2,"e":331}},"dim":["","list.166","listItem.1","paragraph.0"],"code":"[x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."},{"id":"/root/children/166/children/1/children/0/children/0","type":"text","loc":{"start":48463,"end":48467,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.1","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/1/children/0/children/1","type":"strong","loc":{"start":48467,"end":48485,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":6,"e":24}},"dim":["","list.166","listItem.1","paragraph.0","strong.1"],"code":"**Conflict lines**"},{"id":"/root/children/166/children/1/children/0/children/1/children/0","type":"text","loc":{"start":48469,"end":48483,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":8,"e":22}},"dim":["","list.166","listItem.1","paragraph.0","strong.1","text.0"],"code":"Conflict lines"},{"id":"/root/children/166/children/1/children/0/children/2","type":"text","loc":{"start":48485,"end":48487,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":24,"e":26}},"dim":["","list.166","listItem.1","paragraph.0","text.2"],"code":" ("},{"id":"/root/children/166/children/1/children/0/children/3","type":"inlineCode","loc":{"start":48487,"end":48503,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":26,"e":42}},"dim":["","list.166","listItem.1","paragraph.0","inlineCode.3"],"code":"`config.connect`"},{"id":"/root/children/166/children/1/children/0/children/4","type":"text","loc":{"start":48503,"end":48603,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":42,"e":142}},"dim":["","list.166","listItem.1","paragraph.0","text.4"],"code":") — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable "},{"id":"/root/children/166/children/1/children/0/children/5","type":"inlineCode","loc":{"start":48603,"end":48608,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":142,"e":147}},"dim":["","list.166","listItem.1","paragraph.0","inlineCode.5"],"code":"`url`"},{"id":"/root/children/166/children/1/children/0/children/6","type":"text","loc":{"start":48608,"end":48626,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":147,"e":165}},"dim":["","list.166","listItem.1","paragraph.0","text.6"],"code":" (falling back to "},{"id":"/root/children/166/children/1/children/0/children/7","type":"inlineCode","loc":{"start":48626,"end":48632,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":165,"e":171}},"dim":["","list.166","listItem.1","paragraph.0","inlineCode.7"],"code":"`task`"},{"id":"/root/children/166/children/1/children/0/children/8","type":"text","loc":{"start":48632,"end":48647,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":171,"e":186}},"dim":["","list.166","listItem.1","paragraph.0","text.8"],"code":"); the display "},{"id":"/root/children/166/children/1/children/0/children/9","type":"inlineCode","loc":{"start":48647,"end":48653,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":186,"e":192}},"dim":["","list.166","listItem.1","paragraph.0","inlineCode.9"],"code":"`task`"},{"id":"/root/children/166/children/1/children/0/children/10","type":"text","loc":{"start":48653,"end":48693,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":192,"e":232}},"dim":["","list.166","listItem.1","paragraph.0","text.10"],"code":" carries a per-quadrant vote count like "},{"id":"/root/children/166/children/1/children/0/children/11","type":"inlineCode","loc":{"start":48693,"end":48706,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":232,"e":245}},"dim":["","list.166","listItem.1","paragraph.0","inlineCode.11"],"code":"`\"filmy (3)\"`"},{"id":"/root/children/166/children/1/children/0/children/12","type":"text","loc":{"start":48706,"end":48792,"line":{"s":860,"e":860,"code":["- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group."]},"column":{"s":245,"e":331}},"dim":["","list.166","listItem.1","paragraph.0","text.12"],"code":" and would never match across quadrants. All-pairs interconnect inside each url-group."},{"id":"/root/children/166/children/2","type":"listItem","loc":{"start":48793,"end":49015,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":0,"e":222}},"dim":["","list.166","listItem.2"],"code":"- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."},{"id":"/root/children/166/children/2/children/0","type":"paragraph","loc":{"start":48795,"end":49015,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":2,"e":222}},"dim":["","list.166","listItem.2","paragraph.0"],"code":"[x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."},{"id":"/root/children/166/children/2/children/0/children/0","type":"text","loc":{"start":48795,"end":48799,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.2","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/2/children/0/children/1","type":"strong","loc":{"start":48799,"end":48833,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":6,"e":40}},"dim":["","list.166","listItem.2","paragraph.0","strong.1"],"code":"**Font family on Vega text marks**"},{"id":"/root/children/166/children/2/children/0/children/1/children/0","type":"text","loc":{"start":48801,"end":48831,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":8,"e":38}},"dim":["","list.166","listItem.2","paragraph.0","strong.1","text.0"],"code":"Font family on Vega text marks"},{"id":"/root/children/166/children/2/children/0/children/2","type":"text","loc":{"start":48833,"end":48836,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":40,"e":43}},"dim":["","list.166","listItem.2","paragraph.0","text.2"],"code":" — "},{"id":"/root/children/166/children/2/children/0/children/3","type":"inlineCode","loc":{"start":48836,"end":48865,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":43,"e":72}},"dim":["","list.166","listItem.2","paragraph.0","inlineCode.3"],"code":"`font: { value: fontFamily }`"},{"id":"/root/children/166/children/2/children/0/children/4","type":"text","loc":{"start":48865,"end":48888,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":72,"e":95}},"dim":["","list.166","listItem.2","paragraph.0","text.4"],"code":" (Vega encode requires "},{"id":"/root/children/166/children/2/children/0/children/5","type":"inlineCode","loc":{"start":48888,"end":48899,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":95,"e":106}},"dim":["","list.166","listItem.2","paragraph.0","inlineCode.5"],"code":"`{value:…}`"},{"id":"/root/children/166/children/2/children/0/children/6","type":"text","loc":{"start":48899,"end":48915,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":106,"e":122}},"dim":["","list.166","listItem.2","paragraph.0","text.6"],"code":" wrapping; bare "},{"id":"/root/children/166/children/2/children/0/children/7","type":"inlineCode","loc":{"start":48915,"end":48933,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":122,"e":140}},"dim":["","list.166","listItem.2","paragraph.0","inlineCode.7"],"code":"`font: fontFamily`"},{"id":"/root/children/166/children/2/children/0/children/8","type":"text","loc":{"start":48933,"end":49015,"line":{"s":861,"e":861,"code":["- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark."]},"column":{"s":140,"e":222}},"dim":["","list.166","listItem.2","paragraph.0","text.8"],"code":" is silently ignored). Applied to both quadrant labels and the per-task text mark."},{"id":"/root/children/166/children/3","type":"listItem","loc":{"start":49016,"end":49409,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":0,"e":393}},"dim":["","list.166","listItem.3"],"code":"- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."},{"id":"/root/children/166/children/3/children/0","type":"paragraph","loc":{"start":49018,"end":49409,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":2,"e":393}},"dim":["","list.166","listItem.3","paragraph.0"],"code":"[x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."},{"id":"/root/children/166/children/3/children/0/children/0","type":"text","loc":{"start":49018,"end":49022,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.3","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/3/children/0/children/1","type":"strong","loc":{"start":49022,"end":49048,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":6,"e":32}},"dim":["","list.166","listItem.3","paragraph.0","strong.1"],"code":"**`gravityToCorner` mode**"},{"id":"/root/children/166/children/3/children/0/children/1/children/0","type":"inlineCode","loc":{"start":49024,"end":49041,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":8,"e":25}},"dim":["","list.166","listItem.3","paragraph.0","strong.1","inlineCode.0"],"code":"`gravityToCorner`"},{"id":"/root/children/166/children/3/children/0/children/1/children/1","type":"text","loc":{"start":49041,"end":49046,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":25,"e":30}},"dim":["","list.166","listItem.3","paragraph.0","strong.1","text.1"],"code":" mode"},{"id":"/root/children/166/children/3/children/0/children/2","type":"text","loc":{"start":49048,"end":49078,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":32,"e":62}},"dim":["","list.166","listItem.3","paragraph.0","text.2"],"code":" (top-of-file toggle, default "},{"id":"/root/children/166/children/3/children/0/children/3","type":"inlineCode","loc":{"start":49078,"end":49084,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":62,"e":68}},"dim":["","list.166","listItem.3","paragraph.0","inlineCode.3"],"code":"`true`"},{"id":"/root/children/166/children/3/children/0/children/4","type":"text","loc":{"start":49084,"end":49208,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":68,"e":192}},"dim":["","list.166","listItem.3","paragraph.0","text.4"],"code":") — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. "},{"id":"/root/children/166/children/3/children/0/children/5","type":"inlineCode","loc":{"start":49208,"end":49226,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":192,"e":210}},"dim":["","list.166","listItem.3","paragraph.0","inlineCode.5"],"code":"`layoutTasksInBox`"},{"id":"/root/children/166/children/3/children/0/children/6","type":"text","loc":{"start":49226,"end":49295,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":210,"e":279}},"dim":["","list.166","listItem.3","paragraph.0","text.6"],"code":" always anchors top-left, then mirrors x/y based on quadrant corner ("},{"id":"/root/children/166/children/3/children/0/children/7","type":"inlineCode","loc":{"start":49295,"end":49342,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":279,"e":326}},"dim":["","list.166","listItem.3","paragraph.0","inlineCode.7"],"code":"`do=tl, schedule=tr, delegate=bl, eliminate=br`"},{"id":"/root/children/166/children/3/children/0/children/8","type":"text","loc":{"start":49342,"end":49350,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":326,"e":334}},"dim":["","list.166","listItem.3","paragraph.0","text.8"],"code":"). When "},{"id":"/root/children/166/children/3/children/0/children/9","type":"inlineCode","loc":{"start":49350,"end":49357,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":334,"e":341}},"dim":["","list.166","listItem.3","paragraph.0","inlineCode.9"],"code":"`false`"},{"id":"/root/children/166/children/3/children/0/children/10","type":"text","loc":{"start":49357,"end":49409,"line":{"s":862,"e":862,"code":["- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour."]},"column":{"s":341,"e":393}},"dim":["","list.166","listItem.3","paragraph.0","text.10"],"code":", layout reverts to the original centered behaviour."},{"id":"/root/children/166/children/4","type":"listItem","loc":{"start":49410,"end":49613,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":0,"e":203}},"dim":["","list.166","listItem.4"],"code":"- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."},{"id":"/root/children/166/children/4/children/0","type":"paragraph","loc":{"start":49412,"end":49613,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":2,"e":203}},"dim":["","list.166","listItem.4","paragraph.0"],"code":"[x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."},{"id":"/root/children/166/children/4/children/0/children/0","type":"text","loc":{"start":49412,"end":49416,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.4","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/4/children/0/children/1","type":"strong","loc":{"start":49416,"end":49453,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":6,"e":43}},"dim":["","list.166","listItem.4","paragraph.0","strong.1"],"code":"**Quasi-square grid in gravity mode**"},{"id":"/root/children/166/children/4/children/0/children/1/children/0","type":"text","loc":{"start":49418,"end":49451,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":8,"e":41}},"dim":["","list.166","listItem.4","paragraph.0","strong.1","text.0"],"code":"Quasi-square grid in gravity mode"},{"id":"/root/children/166/children/4/children/0/children/2","type":"text","loc":{"start":49453,"end":49456,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":43,"e":46}},"dim":["","list.166","listItem.4","paragraph.0","text.2"],"code":" — "},{"id":"/root/children/166/children/4/children/0/children/3","type":"inlineCode","loc":{"start":49456,"end":49483,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":46,"e":73}},"dim":["","list.166","listItem.4","paragraph.0","inlineCode.3"],"code":"`maxPerRow = ceil(sqrt(N))`"},{"id":"/root/children/166/children/4/children/0/children/4","type":"text","loc":{"start":49483,"end":49613,"line":{"s":863,"e":863,"code":["- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."]},"column":{"s":73,"e":203}},"dim":["","list.166","listItem.4","paragraph.0","text.4"],"code":" forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire."},{"id":"/root/children/166/children/5","type":"listItem","loc":{"start":49614,"end":49837,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":0,"e":223}},"dim":["","list.166","listItem.5"],"code":"- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."},{"id":"/root/children/166/children/5/children/0","type":"paragraph","loc":{"start":49616,"end":49837,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":2,"e":223}},"dim":["","list.166","listItem.5","paragraph.0"],"code":"[x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."},{"id":"/root/children/166/children/5/children/0/children/0","type":"text","loc":{"start":49616,"end":49620,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.5","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/5/children/0/children/1","type":"strong","loc":{"start":49620,"end":49643,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":6,"e":29}},"dim":["","list.166","listItem.5","paragraph.0","strong.1"],"code":"**Stretch to span box**"},{"id":"/root/children/166/children/5/children/0/children/1/children/0","type":"text","loc":{"start":49622,"end":49641,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":8,"e":27}},"dim":["","list.166","listItem.5","paragraph.0","strong.1","text.0"],"code":"Stretch to span box"},{"id":"/root/children/166/children/5/children/0/children/2","type":"text","loc":{"start":49643,"end":49713,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":29,"e":99}},"dim":["","list.166","listItem.5","paragraph.0","text.2"],"code":" — with >1 row, distribute extra vertical gap so the last row reaches "},{"id":"/root/children/166/children/5/children/0/children/3","type":"inlineCode","loc":{"start":49713,"end":49719,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":99,"e":105}},"dim":["","list.166","listItem.5","paragraph.0","inlineCode.3"],"code":"`boxH`"},{"id":"/root/children/166/children/5/children/0/children/4","type":"text","loc":{"start":49719,"end":49753,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":105,"e":139}},"dim":["","list.166","listItem.5","paragraph.0","text.4"],"code":"; same per row for items reaching "},{"id":"/root/children/166/children/5/children/0/children/5","type":"inlineCode","loc":{"start":49753,"end":49759,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":139,"e":145}},"dim":["","list.166","listItem.5","paragraph.0","inlineCode.5"],"code":"`boxW`"},{"id":"/root/children/166/children/5/children/0/children/6","type":"text","loc":{"start":49759,"end":49837,"line":{"s":864,"e":864,"code":["- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center."]},"column":{"s":145,"e":223}},"dim":["","list.166","listItem.5","paragraph.0","text.6"],"code":". Highest stays pinned at the outer corner, lowest lands at the matrix center."},{"id":"/root/children/166/children/6","type":"listItem","loc":{"start":49838,"end":49996,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":0,"e":158}},"dim":["","list.166","listItem.6"],"code":"- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."},{"id":"/root/children/166/children/6/children/0","type":"paragraph","loc":{"start":49840,"end":49996,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":2,"e":158}},"dim":["","list.166","listItem.6","paragraph.0"],"code":"[x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."},{"id":"/root/children/166/children/6/children/0/children/0","type":"text","loc":{"start":49840,"end":49844,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.6","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/6/children/0/children/1","type":"strong","loc":{"start":49844,"end":49863,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":6,"e":25}},"dim":["","list.166","listItem.6","paragraph.0","strong.1"],"code":"**Label clearance**"},{"id":"/root/children/166/children/6/children/0/children/1/children/0","type":"text","loc":{"start":49846,"end":49861,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":8,"e":23}},"dim":["","list.166","listItem.6","paragraph.0","strong.1","text.0"],"code":"Label clearance"},{"id":"/root/children/166/children/6/children/0/children/2","type":"text","loc":{"start":49863,"end":49866,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":25,"e":28}},"dim":["","list.166","listItem.6","paragraph.0","text.2"],"code":" — "},{"id":"/root/children/166/children/6/children/0/children/3","type":"inlineCode","loc":{"start":49866,"end":49885,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":28,"e":47}},"dim":["","list.166","listItem.6","paragraph.0","inlineCode.3"],"code":"`padBottom: 8 → 30`"},{"id":"/root/children/166/children/6/children/0/children/4","type":"text","loc":{"start":49885,"end":49895,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":47,"e":57}},"dim":["","list.166","listItem.6","paragraph.0","text.4"],"code":" (matches "},{"id":"/root/children/166/children/6/children/0/children/5","type":"inlineCode","loc":{"start":49895,"end":49907,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":57,"e":69}},"dim":["","list.166","listItem.6","paragraph.0","inlineCode.5"],"code":"`titleSpace`"},{"id":"/root/children/166/children/6/children/0/children/6","type":"text","loc":{"start":49907,"end":49910,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":69,"e":72}},"dim":["","list.166","listItem.6","paragraph.0","text.6"],"code":"); "},{"id":"/root/children/166/children/6/children/0/children/7","type":"inlineCode","loc":{"start":49910,"end":49920,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":72,"e":82}},"dim":["","list.166","listItem.6","paragraph.0","inlineCode.7"],"code":"`delegate`"},{"id":"/root/children/166/children/6/children/0/children/8","type":"text","loc":{"start":49920,"end":49921,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":82,"e":83}},"dim":["","list.166","listItem.6","paragraph.0","text.8"],"code":"/"},{"id":"/root/children/166/children/6/children/0/children/9","type":"inlineCode","loc":{"start":49921,"end":49932,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":83,"e":94}},"dim":["","list.166","listItem.6","paragraph.0","inlineCode.9"],"code":"`eliminate`"},{"id":"/root/children/166/children/6/children/0/children/10","type":"text","loc":{"start":49932,"end":49996,"line":{"s":865,"e":865,"code":["- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks."]},"column":{"s":94,"e":158}},"dim":["","list.166","listItem.6","paragraph.0","text.10"],"code":" labels sit at the bottom edge and no longer collide with tasks."},{"id":"/root/children/166/children/7","type":"listItem","loc":{"start":49997,"end":50212,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":0,"e":215}},"dim":["","list.166","listItem.7"],"code":"- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."},{"id":"/root/children/166/children/7/children/0","type":"paragraph","loc":{"start":49999,"end":50212,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":2,"e":215}},"dim":["","list.166","listItem.7","paragraph.0"],"code":"[x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."},{"id":"/root/children/166/children/7/children/0/children/0","type":"text","loc":{"start":49999,"end":50003,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.7","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/7/children/0/children/1","type":"strong","loc":{"start":50003,"end":50024,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":6,"e":27}},"dim":["","list.166","listItem.7","paragraph.0","strong.1"],"code":"**2 px quadrant gap**"},{"id":"/root/children/166/children/7/children/0/children/1/children/0","type":"text","loc":{"start":50005,"end":50022,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":8,"e":25}},"dim":["","list.166","listItem.7","paragraph.0","strong.1","text.0"],"code":"2 px quadrant gap"},{"id":"/root/children/166/children/7/children/0/children/2","type":"text","loc":{"start":50024,"end":50031,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":27,"e":34}},"dim":["","list.166","listItem.7","paragraph.0","text.2"],"code":" — new "},{"id":"/root/children/166/children/7/children/0/children/3","type":"inlineCode","loc":{"start":50031,"end":50048,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":34,"e":51}},"dim":["","list.166","listItem.7","paragraph.0","inlineCode.3"],"code":"`quadrantGap = 2`"},{"id":"/root/children/166/children/7/children/0/children/4","type":"text","loc":{"start":50048,"end":50050,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":51,"e":53}},"dim":["","list.166","listItem.7","paragraph.0","text.4"],"code":" ("},{"id":"/root/children/166/children/7/children/0/children/5","type":"inlineCode","loc":{"start":50050,"end":50060,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":53,"e":63}},"dim":["","list.166","listItem.7","paragraph.0","inlineCode.5"],"code":"`half = 1`"},{"id":"/root/children/166/children/7/children/0/children/6","type":"text","loc":{"start":50060,"end":50140,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":63,"e":143}},"dim":["","list.166","listItem.7","paragraph.0","text.6"],"code":"): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by "},{"id":"/root/children/166/children/7/children/0/children/7","type":"inlineCode","loc":{"start":50140,"end":50146,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":143,"e":149}},"dim":["","list.166","listItem.7","paragraph.0","inlineCode.7"],"code":"`half`"},{"id":"/root/children/166/children/7/children/0/children/8","type":"text","loc":{"start":50146,"end":50148,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":149,"e":151}},"dim":["","list.166","listItem.7","paragraph.0","text.8"],"code":", "},{"id":"/root/children/166/children/7/children/0/children/9","type":"inlineCode","loc":{"start":50148,"end":50154,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":151,"e":157}},"dim":["","list.166","listItem.7","paragraph.0","inlineCode.9"],"code":"`boxW`"},{"id":"/root/children/166/children/7/children/0/children/10","type":"text","loc":{"start":50154,"end":50155,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":157,"e":158}},"dim":["","list.166","listItem.7","paragraph.0","text.10"],"code":"/"},{"id":"/root/children/166/children/7/children/0/children/11","type":"inlineCode","loc":{"start":50155,"end":50161,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":158,"e":164}},"dim":["","list.166","listItem.7","paragraph.0","inlineCode.11"],"code":"`boxH`"},{"id":"/root/children/166/children/7/children/0/children/12","type":"text","loc":{"start":50161,"end":50172,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":164,"e":175}},"dim":["","list.166","listItem.7","paragraph.0","text.12"],"code":" shrink by "},{"id":"/root/children/166/children/7/children/0/children/13","type":"inlineCode","loc":{"start":50172,"end":50178,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":175,"e":181}},"dim":["","list.166","listItem.7","paragraph.0","inlineCode.13"],"code":"`half`"},{"id":"/root/children/166/children/7/children/0/children/14","type":"text","loc":{"start":50178,"end":50212,"line":{"s":866,"e":866,"code":["- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel."]},"column":{"s":181,"e":215}},"dim":["","list.166","listItem.7","paragraph.0","text.14"],"code":" so tasks stay inside their panel."},{"id":"/root/children/166/children/8","type":"listItem","loc":{"start":50213,"end":50539,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":0,"e":326}},"dim":["","list.166","listItem.8"],"code":"- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."},{"id":"/root/children/166/children/8/children/0","type":"paragraph","loc":{"start":50215,"end":50539,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":2,"e":326}},"dim":["","list.166","listItem.8","paragraph.0"],"code":"[x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."},{"id":"/root/children/166/children/8/children/0/children/0","type":"text","loc":{"start":50215,"end":50219,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.8","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/8/children/0/children/1","type":"strong","loc":{"start":50219,"end":50247,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":6,"e":34}},"dim":["","list.166","listItem.8","paragraph.0","strong.1"],"code":"**`harmonizeColors` helper**"},{"id":"/root/children/166/children/8/children/0/children/1/children/0","type":"inlineCode","loc":{"start":50221,"end":50238,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":8,"e":25}},"dim":["","list.166","listItem.8","paragraph.0","strong.1","inlineCode.0"],"code":"`harmonizeColors`"},{"id":"/root/children/166/children/8/children/0/children/1/children/1","type":"text","loc":{"start":50238,"end":50245,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":25,"e":32}},"dim":["","list.166","listItem.8","paragraph.0","strong.1","text.1"],"code":" helper"},{"id":"/root/children/166/children/8/children/0/children/2","type":"text","loc":{"start":50247,"end":50330,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":34,"e":117}},"dim":["","list.166","listItem.8","paragraph.0","text.2"],"code":" — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default "},{"id":"/root/children/166/children/8/children/0/children/3","type":"inlineCode","loc":{"start":50330,"end":50352,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":117,"e":139}},"dim":["","list.166","listItem.8","paragraph.0","inlineCode.3"],"code":"`{ s: 0.55, l: 0.78 }`"},{"id":"/root/children/166/children/8/children/0/children/4","type":"text","loc":{"start":50352,"end":50420,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":139,"e":207}},"dim":["","list.166","listItem.8","paragraph.0","text.4"],"code":") so the four quadrant fills feel like one palette family. Exported "},{"id":"/root/children/166/children/8/children/0/children/5","type":"inlineCode","loc":{"start":50420,"end":50436,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":207,"e":223}},"dim":["","list.166","listItem.8","paragraph.0","inlineCode.5"],"code":"`quadrantColors`"},{"id":"/root/children/166/children/8/children/0/children/6","type":"text","loc":{"start":50436,"end":50513,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":223,"e":300}},"dim":["","list.166","listItem.8","paragraph.0","text.6"],"code":" stays as the raw Dracula palette; harmonised set is computed locally inside "},{"id":"/root/children/166/children/8/children/0/children/7","type":"inlineCode","loc":{"start":50513,"end":50538,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":300,"e":325}},"dim":["","list.166","listItem.8","paragraph.0","inlineCode.7"],"code":"`tasksToEisenhoverMatrix`"},{"id":"/root/children/166/children/8/children/0/children/8","type":"text","loc":{"start":50538,"end":50539,"line":{"s":867,"e":867,"code":["- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`."]},"column":{"s":325,"e":326}},"dim":["","list.166","listItem.8","paragraph.0","text.8"],"code":"."},{"id":"/root/children/166/children/9","type":"listItem","loc":{"start":50540,"end":50895,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":0,"e":355}},"dim":["","list.166","listItem.9"],"code":"- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."},{"id":"/root/children/166/children/9/children/0","type":"paragraph","loc":{"start":50542,"end":50895,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":2,"e":355}},"dim":["","list.166","listItem.9","paragraph.0"],"code":"[x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."},{"id":"/root/children/166/children/9/children/0/children/0","type":"text","loc":{"start":50542,"end":50546,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.9","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/9/children/0/children/1","type":"strong","loc":{"start":50546,"end":50588,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":6,"e":48}},"dim":["","list.166","listItem.9","paragraph.0","strong.1"],"code":"**Density culling fontSize re-evaluation**"},{"id":"/root/children/166/children/9/children/0/children/1/children/0","type":"text","loc":{"start":50548,"end":50586,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":8,"e":46}},"dim":["","list.166","listItem.9","paragraph.0","strong.1","text.0"],"code":"Density culling fontSize re-evaluation"},{"id":"/root/children/166/children/9/children/0/children/2","type":"text","loc":{"start":50588,"end":50648,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":48,"e":108}},"dim":["","list.166","listItem.9","paragraph.0","text.2"],"code":" — after density culling drops lowest-scoring tasks to meet "},{"id":"/root/children/166/children/9/children/0/children/3","type":"inlineCode","loc":{"start":50648,"end":50664,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":108,"e":124}},"dim":["","list.166","listItem.9","paragraph.0","inlineCode.3"],"code":"`densityOptimum`"},{"id":"/root/children/166/children/9/children/0/children/4","type":"text","loc":{"start":50664,"end":50666,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":124,"e":126}},"dim":["","list.166","listItem.9","paragraph.0","text.4"],"code":", "},{"id":"/root/children/166/children/9/children/0/children/5","type":"inlineCode","loc":{"start":50666,"end":50681,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":126,"e":141}},"dim":["","list.166","listItem.9","paragraph.0","inlineCode.5"],"code":"`fontSizeRange`"},{"id":"/root/children/166/children/9/children/0/children/6","type":"text","loc":{"start":50681,"end":50731,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":141,"e":191}},"dim":["","list.166","listItem.9","paragraph.0","text.6"],"code":" is now recalculated from the reduced task count ("},{"id":"/root/children/166/children/9/children/0/children/7","type":"inlineCode","loc":{"start":50731,"end":50760,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":191,"e":220}},"dim":["","list.166","listItem.9","paragraph.0","inlineCode.7"],"code":"`laid.length / (boxW * boxH)`"},{"id":"/root/children/166/children/9/children/0/children/8","type":"text","loc":{"start":50760,"end":50895,"line":{"s":868,"e":868,"code":["- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."]},"column":{"s":220,"e":355}},"dim":["","list.166","listItem.9","paragraph.0","text.8"],"code":") before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be."},{"id":"/root/children/166/children/10","type":"listItem","loc":{"start":50896,"end":51829,"line":{"s":869,"e":871,"code":["- [x] **`showBorder` box width fix** — Two corrections:","  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.","  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":0,"e":594}},"dim":["","list.166","listItem.10"],"code":"- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."},{"id":"/root/children/166/children/10/children/0","type":"paragraph","loc":{"start":50898,"end":50951,"line":{"s":869,"e":869,"code":["- [x] **`showBorder` box width fix** — Two corrections:"]},"column":{"s":2,"e":55}},"dim":["","list.166","listItem.10","paragraph.0"],"code":"[x] **`showBorder` box width fix** — Two corrections:"},{"id":"/root/children/166/children/10/children/0/children/0","type":"text","loc":{"start":50898,"end":50902,"line":{"s":869,"e":869,"code":["- [x] **`showBorder` box width fix** — Two corrections:"]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.10","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/10/children/0/children/1","type":"strong","loc":{"start":50902,"end":50932,"line":{"s":869,"e":869,"code":["- [x] **`showBorder` box width fix** — Two corrections:"]},"column":{"s":6,"e":36}},"dim":["","list.166","listItem.10","paragraph.0","strong.1"],"code":"**`showBorder` box width fix**"},{"id":"/root/children/166/children/10/children/0/children/1/children/0","type":"inlineCode","loc":{"start":50904,"end":50916,"line":{"s":869,"e":869,"code":["- [x] **`showBorder` box width fix** — Two corrections:"]},"column":{"s":8,"e":20}},"dim":["","list.166","listItem.10","paragraph.0","strong.1","inlineCode.0"],"code":"`showBorder`"},{"id":"/root/children/166/children/10/children/0/children/1/children/1","type":"text","loc":{"start":50916,"end":50930,"line":{"s":869,"e":869,"code":["- [x] **`showBorder` box width fix** — Two corrections:"]},"column":{"s":20,"e":34}},"dim":["","list.166","listItem.10","paragraph.0","strong.1","text.1"],"code":" box width fix"},{"id":"/root/children/166/children/10/children/0/children/2","type":"text","loc":{"start":50932,"end":50951,"line":{"s":869,"e":869,"code":["- [x] **`showBorder` box width fix** — Two corrections:"]},"column":{"s":36,"e":55}},"dim":["","list.166","listItem.10","paragraph.0","text.2"],"code":" — Two corrections:"},{"id":"/root/children/166/children/10/children/1","type":"list","loc":{"start":50954,"end":51829,"line":{"s":870,"e":871,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.","  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":2,"e":594}},"dim":["","list.166","listItem.10","list.1"],"code":"- **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).","symbName":"list","symbRange":[null,null],"symbRangeL":[870,871],"outerCode":"","outerHtml":""},{"id":"/root/children/166/children/10/children/1/children/0","type":"listItem","loc":{"start":50954,"end":51234,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":2,"e":282}},"dim":["","list.166","listItem.10","list.1","listItem.0"],"code":"- **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."},{"id":"/root/children/166/children/10/children/1/children/0/children/0","type":"paragraph","loc":{"start":50956,"end":51234,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":4,"e":282}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0"],"code":"**First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/0","type":"strong","loc":{"start":50956,"end":50992,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":4,"e":40}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","strong.0"],"code":"**First pass (all layout engines):**"},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/0/children/0","type":"text","loc":{"start":50958,"end":50990,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":6,"e":38}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","strong.0","text.0"],"code":"First pass (all layout engines):"},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/1","type":"text","loc":{"start":50992,"end":50993,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":40,"e":41}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","text.1"],"code":" "},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/2","type":"inlineCode","loc":{"start":50993,"end":51000,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":41,"e":48}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","inlineCode.2"],"code":"`width`"},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/3","type":"text","loc":{"start":51000,"end":51005,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":48,"e":53}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","text.3"],"code":" was "},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/4","type":"inlineCode","loc":{"start":51005,"end":51066,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":53,"e":114}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","inlineCode.4"],"code":"`estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)`"},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/5","type":"text","loc":{"start":51066,"end":51082,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":114,"e":130}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","text.5"],"code":" — the trailing "},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/6","type":"inlineCode","loc":{"start":51082,"end":51088,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":130,"e":136}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","inlineCode.6"],"code":"`\", \"`"},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/7","type":"text","loc":{"start":51088,"end":51123,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":136,"e":171}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","text.7"],"code":" inflated the box by ~2 chars when "},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/8","type":"inlineCode","loc":{"start":51123,"end":51134,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":171,"e":182}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","inlineCode.8"],"code":"`miniLabel`"},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/9","type":"text","loc":{"start":51134,"end":51157,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":182,"e":205}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","text.9"],"code":" was empty. Changed to "},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/10","type":"inlineCode","loc":{"start":51157,"end":51233,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":205,"e":281}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","inlineCode.10"],"code":"`estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`"},{"id":"/root/children/166/children/10/children/1/children/0/children/0/children/11","type":"text","loc":{"start":51233,"end":51234,"line":{"s":870,"e":870,"code":["  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`."]},"column":{"s":281,"e":282}},"dim":["","list.166","listItem.10","list.1","listItem.0","paragraph.0","text.11"],"code":"."},{"id":"/root/children/166/children/10/children/1/children/1","type":"listItem","loc":{"start":51237,"end":51829,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":2,"e":594}},"dim":["","list.166","listItem.10","list.1","listItem.1"],"code":"- **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."},{"id":"/root/children/166/children/10/children/1/children/1/children/0","type":"paragraph","loc":{"start":51239,"end":51829,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":4,"e":594}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0"],"code":"**Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/0","type":"strong","loc":{"start":51239,"end":51295,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":4,"e":60}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","strong.0"],"code":"**Second pass (eisenhover-matrix.js positioning loop):**"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/0/children/0","type":"text","loc":{"start":51241,"end":51293,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":6,"e":58}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","strong.0","text.0"],"code":"Second pass (eisenhover-matrix.js positioning loop):"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/1","type":"text","loc":{"start":51295,"end":51322,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":60,"e":87}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.1"],"code":" The border rect was using "},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/2","type":"inlineCode","loc":{"start":51322,"end":51329,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":87,"e":94}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.2"],"code":"`width`"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/3","type":"text","loc":{"start":51329,"end":51331,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":94,"e":96}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.3"],"code":" ("},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/4","type":"inlineCode","loc":{"start":51331,"end":51402,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":96,"e":167}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.4"],"code":"`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/5","type":"text","loc":{"start":51402,"end":51428,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":167,"e":193}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.5"],"code":"), but the miniLabel is a "},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/6","type":"strong","loc":{"start":51428,"end":51455,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":193,"e":220}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","strong.6"],"code":"**separate Vega text mark**"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/6/children/0","type":"text","loc":{"start":51430,"end":51453,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":195,"e":218}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","strong.6","text.0"],"code":"separate Vega text mark"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/7","type":"text","loc":{"start":51455,"end":51562,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":220,"e":327}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.7"],"code":" at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed "},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/8","type":"inlineCode","loc":{"start":51562,"end":51583,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":327,"e":348}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.8"],"code":"`width: t.width || 0`"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/9","type":"text","loc":{"start":51583,"end":51586,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":348,"e":351}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.9"],"code":" → "},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/10","type":"inlineCode","loc":{"start":51586,"end":51622,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":351,"e":387}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.10"],"code":"`width: t.taskWidth || t.width || 0`"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/11","type":"text","loc":{"start":51622,"end":51664,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":387,"e":429}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.11"],"code":" so the rect matches the task-only width. "},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/12","type":"inlineCode","loc":{"start":51664,"end":51675,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":429,"e":440}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.12"],"code":"`taskWidth`"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/13","type":"text","loc":{"start":51675,"end":51679,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":440,"e":444}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.13"],"code":" is "},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/14","type":"inlineCode","loc":{"start":51679,"end":51726,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":444,"e":491}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","inlineCode.14"],"code":"`estimateTextWidth(task, fontSize, fontWeight)`"},{"id":"/root/children/166/children/10/children/1/children/1/children/0/children/15","type":"text","loc":{"start":51726,"end":51829,"line":{"s":871,"e":871,"code":["  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."]},"column":{"s":491,"e":594}},"dim":["","list.166","listItem.10","list.1","listItem.1","paragraph.0","text.15"],"code":" without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal)."},{"id":"/root/children/166/children/11","type":"listItem","loc":{"start":51830,"end":52485,"line":{"s":872,"e":878,"code":["- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:","  1. Compute `mean` and `stdDev` of task scores in quadrant","  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`","  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers","  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`","  - Controlled by `config.densityOptimum` (default `0.6`)","  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":0,"e":123}},"dim":["","list.166","listItem.11"],"code":"- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"},{"id":"/root/children/166/children/11/children/0","type":"paragraph","loc":{"start":51832,"end":51943,"line":{"s":872,"e":872,"code":["- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:"]},"column":{"s":2,"e":113}},"dim":["","list.166","listItem.11","paragraph.0"],"code":"[x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:"},{"id":"/root/children/166/children/11/children/0/children/0","type":"text","loc":{"start":51832,"end":51836,"line":{"s":872,"e":872,"code":["- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:"]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.11","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/11/children/0/children/1","type":"strong","loc":{"start":51836,"end":51868,"line":{"s":872,"e":872,"code":["- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:"]},"column":{"s":6,"e":38}},"dim":["","list.166","listItem.11","paragraph.0","strong.1"],"code":"**StdDev-based density culling**"},{"id":"/root/children/166/children/11/children/0/children/1/children/0","type":"text","loc":{"start":51838,"end":51866,"line":{"s":872,"e":872,"code":["- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:"]},"column":{"s":8,"e":36}},"dim":["","list.166","listItem.11","paragraph.0","strong.1","text.0"],"code":"StdDev-based density culling"},{"id":"/root/children/166/children/11/children/0/children/2","type":"text","loc":{"start":51868,"end":51943,"line":{"s":872,"e":872,"code":["- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:"]},"column":{"s":38,"e":113}},"dim":["","list.166","listItem.11","paragraph.0","text.2"],"code":" — Replaced simple ascending-score drop with two-pass culling per quadrant:"},{"id":"/root/children/166/children/11/children/1","type":"list","loc":{"start":51946,"end":52306,"line":{"s":873,"e":877,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant","  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`","  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers","  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`","  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":2,"e":2}},"dim":["","list.166","listItem.11","list.1"],"code":"1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  ","symbName":"list","symbRange":[52306,52485],"symbRangeL":[873,876],"outerCode":"  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers","outerHtml":"<ol><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li></ol>"},{"id":"/root/children/166/children/11/children/1/children/0","type":"listItem","loc":{"start":51946,"end":52003,"line":{"s":873,"e":873,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant"]},"column":{"s":2,"e":59}},"dim":["","list.166","listItem.11","list.1","listItem.0"],"code":"1. Compute `mean` and `stdDev` of task scores in quadrant"},{"id":"/root/children/166/children/11/children/1/children/0/children/0","type":"paragraph","loc":{"start":51949,"end":52003,"line":{"s":873,"e":873,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant"]},"column":{"s":5,"e":59}},"dim":["","list.166","listItem.11","list.1","listItem.0","paragraph.0"],"code":"Compute `mean` and `stdDev` of task scores in quadrant"},{"id":"/root/children/166/children/11/children/1/children/0/children/0/children/0","type":"text","loc":{"start":51949,"end":51957,"line":{"s":873,"e":873,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant"]},"column":{"s":5,"e":13}},"dim":["","list.166","listItem.11","list.1","listItem.0","paragraph.0","text.0"],"code":"Compute "},{"id":"/root/children/166/children/11/children/1/children/0/children/0/children/1","type":"inlineCode","loc":{"start":51957,"end":51963,"line":{"s":873,"e":873,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant"]},"column":{"s":13,"e":19}},"dim":["","list.166","listItem.11","list.1","listItem.0","paragraph.0","inlineCode.1"],"code":"`mean`"},{"id":"/root/children/166/children/11/children/1/children/0/children/0/children/2","type":"text","loc":{"start":51963,"end":51968,"line":{"s":873,"e":873,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant"]},"column":{"s":19,"e":24}},"dim":["","list.166","listItem.11","list.1","listItem.0","paragraph.0","text.2"],"code":" and "},{"id":"/root/children/166/children/11/children/1/children/0/children/0/children/3","type":"inlineCode","loc":{"start":51968,"end":51976,"line":{"s":873,"e":873,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant"]},"column":{"s":24,"e":32}},"dim":["","list.166","listItem.11","list.1","listItem.0","paragraph.0","inlineCode.3"],"code":"`stdDev`"},{"id":"/root/children/166/children/11/children/1/children/0/children/0/children/4","type":"text","loc":{"start":51976,"end":52003,"line":{"s":873,"e":873,"code":["  1. Compute `mean` and `stdDev` of task scores in quadrant"]},"column":{"s":32,"e":59}},"dim":["","list.166","listItem.11","list.1","listItem.0","paragraph.0","text.4"],"code":" of task scores in quadrant"},{"id":"/root/children/166/children/11/children/1/children/1","type":"listItem","loc":{"start":52006,"end":52109,"line":{"s":874,"e":874,"code":["  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"]},"column":{"s":2,"e":105}},"dim":["","list.166","listItem.11","list.1","listItem.1"],"code":"2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"},{"id":"/root/children/166/children/11/children/1/children/1/children/0","type":"paragraph","loc":{"start":52009,"end":52109,"line":{"s":874,"e":874,"code":["  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"]},"column":{"s":5,"e":105}},"dim":["","list.166","listItem.11","list.1","listItem.1","paragraph.0"],"code":"**First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"},{"id":"/root/children/166/children/11/children/1/children/1/children/0/children/0","type":"strong","loc":{"start":52009,"end":52024,"line":{"s":874,"e":874,"code":["  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"]},"column":{"s":5,"e":20}},"dim":["","list.166","listItem.11","list.1","listItem.1","paragraph.0","strong.0"],"code":"**First pass:**"},{"id":"/root/children/166/children/11/children/1/children/1/children/0/children/0/children/0","type":"text","loc":{"start":52011,"end":52022,"line":{"s":874,"e":874,"code":["  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"]},"column":{"s":7,"e":18}},"dim":["","list.166","listItem.11","list.1","listItem.1","paragraph.0","strong.0","text.0"],"code":"First pass:"},{"id":"/root/children/166/children/11/children/1/children/1/children/0/children/1","type":"text","loc":{"start":52024,"end":52093,"line":{"s":874,"e":874,"code":["  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"]},"column":{"s":20,"e":89}},"dim":["","list.166","listItem.11","list.1","listItem.1","paragraph.0","text.1"],"code":" Drop statistical outliers (score < mean − 1×stdDev) until density ≤ "},{"id":"/root/children/166/children/11/children/1/children/1/children/0/children/2","type":"inlineCode","loc":{"start":52093,"end":52109,"line":{"s":874,"e":874,"code":["  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`"]},"column":{"s":89,"e":105}},"dim":["","list.166","listItem.11","list.1","listItem.1","paragraph.0","inlineCode.2"],"code":"`densityOptimum`"},{"id":"/root/children/166/children/11/children/1/children/2","type":"listItem","loc":{"start":52112,"end":52191,"line":{"s":875,"e":875,"code":["  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers"]},"column":{"s":2,"e":81}},"dim":["","list.166","listItem.11","list.1","listItem.2"],"code":"3. **Second pass:** If still over threshold, drop lowest remaining non-outliers"},{"id":"/root/children/166/children/11/children/1/children/2/children/0","type":"paragraph","loc":{"start":52115,"end":52191,"line":{"s":875,"e":875,"code":["  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers"]},"column":{"s":5,"e":81}},"dim":["","list.166","listItem.11","list.1","listItem.2","paragraph.0"],"code":"**Second pass:** If still over threshold, drop lowest remaining non-outliers"},{"id":"/root/children/166/children/11/children/1/children/2/children/0/children/0","type":"strong","loc":{"start":52115,"end":52131,"line":{"s":875,"e":875,"code":["  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers"]},"column":{"s":5,"e":21}},"dim":["","list.166","listItem.11","list.1","listItem.2","paragraph.0","strong.0"],"code":"**Second pass:**"},{"id":"/root/children/166/children/11/children/1/children/2/children/0/children/0/children/0","type":"text","loc":{"start":52117,"end":52129,"line":{"s":875,"e":875,"code":["  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers"]},"column":{"s":7,"e":19}},"dim":["","list.166","listItem.11","list.1","listItem.2","paragraph.0","strong.0","text.0"],"code":"Second pass:"},{"id":"/root/children/166/children/11/children/1/children/2/children/0/children/1","type":"text","loc":{"start":52131,"end":52191,"line":{"s":875,"e":875,"code":["  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers"]},"column":{"s":21,"e":81}},"dim":["","list.166","listItem.11","list.1","listItem.2","paragraph.0","text.1"],"code":" If still over threshold, drop lowest remaining non-outliers"},{"id":"/root/children/166/children/11/children/1/children/3","type":"listItem","loc":{"start":52194,"end":52303,"line":{"s":876,"e":876,"code":["  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"]},"column":{"s":2,"e":111}},"dim":["","list.166","listItem.11","list.1","listItem.3"],"code":"4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"},{"id":"/root/children/166/children/11/children/1/children/3/children/0","type":"paragraph","loc":{"start":52197,"end":52303,"line":{"s":876,"e":876,"code":["  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"]},"column":{"s":5,"e":111}},"dim":["","list.166","listItem.11","list.1","listItem.3","paragraph.0"],"code":"Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"},{"id":"/root/children/166/children/11/children/1/children/3/children/0/children/0","type":"text","loc":{"start":52197,"end":52209,"line":{"s":876,"e":876,"code":["  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"]},"column":{"s":5,"e":17}},"dim":["","list.166","listItem.11","list.1","listItem.3","paragraph.0","text.0"],"code":"Recalculate "},{"id":"/root/children/166/children/11/children/1/children/3/children/0/children/1","type":"inlineCode","loc":{"start":52209,"end":52224,"line":{"s":876,"e":876,"code":["  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"]},"column":{"s":17,"e":32}},"dim":["","list.166","listItem.11","list.1","listItem.3","paragraph.0","inlineCode.1"],"code":"`fontSizeRange`"},{"id":"/root/children/166/children/11/children/1/children/3/children/0/children/2","type":"text","loc":{"start":52224,"end":52290,"line":{"s":876,"e":876,"code":["  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"]},"column":{"s":32,"e":98}},"dim":["","list.166","listItem.11","list.1","listItem.3","paragraph.0","text.2"],"code":" on reduced density (fewer tasks → bigger font) and re-layout via "},{"id":"/root/children/166/children/11/children/1/children/3/children/0/children/3","type":"inlineCode","loc":{"start":52290,"end":52303,"line":{"s":876,"e":876,"code":["  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`"]},"column":{"s":98,"e":111}},"dim":["","list.166","listItem.11","list.1","listItem.3","paragraph.0","inlineCode.3"],"code":"`runLayout()`"},{"id":"/root/children/166/children/11/children/2","type":"list","loc":{"start":52306,"end":52485,"line":{"s":877,"e":878,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)","  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":2,"e":123}},"dim":["","list.166","listItem.11","list.2"],"code":"- Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar","symbName":"list","symbRange":[null,null],"symbRangeL":[877,878],"outerCode":"","outerHtml":""},{"id":"/root/children/166/children/11/children/2/children/0","type":"listItem","loc":{"start":52306,"end":52361,"line":{"s":877,"e":877,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":2,"e":57}},"dim":["","list.166","listItem.11","list.2","listItem.0"],"code":"- Controlled by `config.densityOptimum` (default `0.6`)"},{"id":"/root/children/166/children/11/children/2/children/0/children/0","type":"paragraph","loc":{"start":52308,"end":52361,"line":{"s":877,"e":877,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":4,"e":57}},"dim":["","list.166","listItem.11","list.2","listItem.0","paragraph.0"],"code":"Controlled by `config.densityOptimum` (default `0.6`)"},{"id":"/root/children/166/children/11/children/2/children/0/children/0/children/0","type":"text","loc":{"start":52308,"end":52322,"line":{"s":877,"e":877,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":4,"e":18}},"dim":["","list.166","listItem.11","list.2","listItem.0","paragraph.0","text.0"],"code":"Controlled by "},{"id":"/root/children/166/children/11/children/2/children/0/children/0/children/1","type":"inlineCode","loc":{"start":52322,"end":52345,"line":{"s":877,"e":877,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":18,"e":41}},"dim":["","list.166","listItem.11","list.2","listItem.0","paragraph.0","inlineCode.1"],"code":"`config.densityOptimum`"},{"id":"/root/children/166/children/11/children/2/children/0/children/0/children/2","type":"text","loc":{"start":52345,"end":52355,"line":{"s":877,"e":877,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":41,"e":51}},"dim":["","list.166","listItem.11","list.2","listItem.0","paragraph.0","text.2"],"code":" (default "},{"id":"/root/children/166/children/11/children/2/children/0/children/0/children/3","type":"inlineCode","loc":{"start":52355,"end":52360,"line":{"s":877,"e":877,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":51,"e":56}},"dim":["","list.166","listItem.11","list.2","listItem.0","paragraph.0","inlineCode.3"],"code":"`0.6`"},{"id":"/root/children/166/children/11/children/2/children/0/children/0/children/4","type":"text","loc":{"start":52360,"end":52361,"line":{"s":877,"e":877,"code":["  - Controlled by `config.densityOptimum` (default `0.6`)"]},"column":{"s":56,"e":57}},"dim":["","list.166","listItem.11","list.2","listItem.0","paragraph.0","text.4"],"code":")"},{"id":"/root/children/166/children/11/children/2/children/1","type":"listItem","loc":{"start":52364,"end":52485,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":2,"e":123}},"dim":["","list.166","listItem.11","list.2","listItem.1"],"code":"- `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"},{"id":"/root/children/166/children/11/children/2/children/1/children/0","type":"paragraph","loc":{"start":52366,"end":52485,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":4,"e":123}},"dim":["","list.166","listItem.11","list.2","listItem.1","paragraph.0"],"code":"`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"},{"id":"/root/children/166/children/11/children/2/children/1/children/0/children/0","type":"inlineCode","loc":{"start":52366,"end":52382,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":4,"e":20}},"dim":["","list.166","listItem.11","list.2","listItem.1","paragraph.0","inlineCode.0"],"code":"`_densityCulled`"},{"id":"/root/children/166/children/11/children/2/children/1/children/0/children/1","type":"text","loc":{"start":52382,"end":52428,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":20,"e":66}},"dim":["","list.166","listItem.11","list.2","listItem.1","paragraph.0","text.1"],"code":" boolean on returned spec object; captured by "},{"id":"/root/children/166/children/11/children/2/children/1/children/0/children/2","type":"inlineCode","loc":{"start":52428,"end":52450,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":66,"e":88}},"dim":["","list.166","listItem.11","list.2","listItem.1","paragraph.0","inlineCode.2"],"code":"`cmdVotingCampaign.js`"},{"id":"/root/children/166/children/11/children/2/children/1/children/0/children/3","type":"text","loc":{"start":52450,"end":52461,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":88,"e":99}},"dim":["","list.166","listItem.11","list.2","listItem.1","paragraph.0","text.3"],"code":" to append "},{"id":"/root/children/166/children/11/children/2/children/1/children/0/children/4","type":"inlineCode","loc":{"start":52461,"end":52471,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":99,"e":109}},"dim":["","list.166","listItem.11","list.2","listItem.1","paragraph.0","inlineCode.4"],"code":"`[culled]`"},{"id":"/root/children/166/children/11/children/2/children/1/children/0/children/5","type":"text","loc":{"start":52471,"end":52485,"line":{"s":878,"e":878,"code":["  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar"]},"column":{"s":109,"e":123}},"dim":["","list.166","listItem.11","list.2","listItem.1","paragraph.0","text.5"],"code":" to status bar"},{"id":"/root/children/166/children/12","type":"listItem","loc":{"start":52486,"end":52944,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":0,"e":458}},"dim":["","list.166","listItem.12"],"code":"- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."},{"id":"/root/children/166/children/12/children/0","type":"paragraph","loc":{"start":52488,"end":52944,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":2,"e":458}},"dim":["","list.166","listItem.12","paragraph.0"],"code":"[x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."},{"id":"/root/children/166/children/12/children/0/children/0","type":"text","loc":{"start":52488,"end":52492,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":2,"e":6}},"dim":["","list.166","listItem.12","paragraph.0","text.0"],"code":"[x] "},{"id":"/root/children/166/children/12/children/0/children/1","type":"strong","loc":{"start":52492,"end":52533,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":6,"e":47}},"dim":["","list.166","listItem.12","paragraph.0","strong.1"],"code":"**Font scale cap now width-proportional**"},{"id":"/root/children/166/children/12/children/0/children/1/children/0","type":"text","loc":{"start":52494,"end":52531,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":8,"e":45}},"dim":["","list.166","listItem.12","paragraph.0","strong.1","text.0"],"code":"Font scale cap now width-proportional"},{"id":"/root/children/166/children/12/children/0/children/2","type":"text","loc":{"start":52533,"end":52555,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":47,"e":69}},"dim":["","list.166","listItem.12","paragraph.0","text.2"],"code":" — Replaced hardcoded "},{"id":"/root/children/166/children/12/children/0/children/3","type":"inlineCode","loc":{"start":52555,"end":52560,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":69,"e":74}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.3"],"code":"`2.0`"},{"id":"/root/children/166/children/12/children/0/children/4","type":"text","loc":{"start":52560,"end":52578,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":74,"e":92}},"dim":["","list.166","listItem.12","paragraph.0","text.4"],"code":" (and the earlier "},{"id":"/root/children/166/children/12/children/0/children/5","type":"inlineCode","loc":{"start":52578,"end":52600,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":92,"e":114}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.5"],"code":"`isMobile ? 1.3 : 2.0`"},{"id":"/root/children/166/children/12/children/0/children/6","type":"text","loc":{"start":52600,"end":52607,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":114,"e":121}},"dim":["","list.166","listItem.12","paragraph.0","text.6"],"code":") with "},{"id":"/root/children/166/children/12/children/0/children/7","type":"inlineCode","loc":{"start":52607,"end":52619,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":121,"e":133}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.7"],"code":"`boxW / 180`"},{"id":"/root/children/166/children/12/children/0/children/8","type":"text","loc":{"start":52619,"end":52627,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":133,"e":141}},"dim":["","list.166","listItem.12","paragraph.0","text.8"],"code":" in the "},{"id":"/root/children/166/children/12/children/0/children/9","type":"inlineCode","loc":{"start":52627,"end":52639,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":141,"e":153}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.9"],"code":"`Math.min()`"},{"id":"/root/children/166/children/12/children/0/children/10","type":"text","loc":{"start":52639,"end":52673,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":153,"e":187}},"dim":["","list.166","listItem.12","paragraph.0","text.10"],"code":" chain. Also fixed the same stale "},{"id":"/root/children/166/children/12/children/0/children/11","type":"inlineCode","loc":{"start":52673,"end":52678,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":187,"e":192}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.11"],"code":"`2.0`"},{"id":"/root/children/166/children/12/children/0/children/12","type":"text","loc":{"start":52678,"end":52758,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":192,"e":272}},"dim":["","list.166","listItem.12","paragraph.0","text.12"],"code":" in the re-layout after density culling. This adapts naturally to any viewport: "},{"id":"/root/children/166/children/12/children/0/children/13","type":"inlineCode","loc":{"start":52758,"end":52768,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":272,"e":282}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.13"],"code":"`boxW=194`"},{"id":"/root/children/166/children/12/children/0/children/14","type":"text","loc":{"start":52768,"end":52799,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":282,"e":313}},"dim":["","list.166","listItem.12","paragraph.0","text.14"],"code":" (mobile 390×844) → cap ~1.08, "},{"id":"/root/children/166/children/12/children/0/children/15","type":"inlineCode","loc":{"start":52799,"end":52810,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":313,"e":324}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.15"],"code":"`boxW=400+`"},{"id":"/root/children/166/children/12/children/0/children/16","type":"text","loc":{"start":52810,"end":52856,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":324,"e":370}},"dim":["","list.166","listItem.12","paragraph.0","text.16"],"code":" (desktop) → cap 2.0 (unchanged). No explicit "},{"id":"/root/children/166/children/12/children/0/children/17","type":"inlineCode","loc":{"start":52856,"end":52866,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":370,"e":380}},"dim":["","list.166","listItem.12","paragraph.0","inlineCode.17"],"code":"`isMobile`"},{"id":"/root/children/166/children/12/children/0/children/18","type":"text","loc":{"start":52866,"end":52944,"line":{"s":879,"e":879,"code":["- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula."]},"column":{"s":380,"e":458}},"dim":["","list.166","listItem.12","paragraph.0","text.18"],"code":" check needed. Both initial layout and culling re-layout use the same formula."},{"id":"/root/children/167","type":"heading","loc":{"start":52946,"end":52952,"line":{"s":881,"e":881,"code":["## Use"]},"column":{"s":0,"e":6}},"dim":["","heading.167"],"code":"## Use","symbName":"heading","symbRange":[52954,53396],"symbRangeL":[881,887],"outerCode":"\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.","outerHtml":"\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>"},{"id":"/root/children/167/children/0","type":"text","loc":{"start":52949,"end":52952,"line":{"s":881,"e":881,"code":["## Use"]},"column":{"s":3,"e":6}},"dim":["","heading.167","text.0"],"code":"Use"},{"id":"/root/children/168","type":"list","loc":{"start":52954,"end":53396,"line":{"s":883,"e":886,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.","- Called from:","  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.","  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":0,"e":190}},"dim":["","list.168"],"code":"- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.","symbName":"list","symbRange":[53398,53724],"symbRangeL":[883,896],"outerCode":"- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed","outerHtml":"<ul><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>"},{"id":"/root/children/168/children/0","type":"listItem","loc":{"start":52954,"end":53062,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":0,"e":108}},"dim":["","list.168","listItem.0"],"code":"- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."},{"id":"/root/children/168/children/0/children/0","type":"paragraph","loc":{"start":52956,"end":53062,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":2,"e":108}},"dim":["","list.168","listItem.0","paragraph.0"],"code":"`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."},{"id":"/root/children/168/children/0/children/0/children/0","type":"inlineCode","loc":{"start":52956,"end":52985,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":2,"e":31}},"dim":["","list.168","listItem.0","paragraph.0","inlineCode.0"],"code":"`player/eisenhover-matrix.js`"},{"id":"/root/children/168/children/0/children/0/children/1","type":"text","loc":{"start":52985,"end":52994,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":31,"e":40}},"dim":["","list.168","listItem.0","paragraph.0","text.1"],"code":" exports "},{"id":"/root/children/168/children/0/children/0/children/2","type":"inlineCode","loc":{"start":52994,"end":53040,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":40,"e":86}},"dim":["","list.168","listItem.0","paragraph.0","inlineCode.2"],"code":"`tasksToEisenhoverMatrix(ssss, tasks, config)`"},{"id":"/root/children/168/children/0/children/0/children/3","type":"text","loc":{"start":53040,"end":53045,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":86,"e":91}},"dim":["","list.168","listItem.0","paragraph.0","text.3"],"code":" and "},{"id":"/root/children/168/children/0/children/0/children/4","type":"inlineCode","loc":{"start":53045,"end":53061,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":91,"e":107}},"dim":["","list.168","listItem.0","paragraph.0","inlineCode.4"],"code":"`quadrantColors`"},{"id":"/root/children/168/children/0/children/0/children/5","type":"text","loc":{"start":53061,"end":53062,"line":{"s":883,"e":883,"code":["- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`."]},"column":{"s":107,"e":108}},"dim":["","list.168","listItem.0","paragraph.0","text.5"],"code":"."},{"id":"/root/children/168/children/1","type":"listItem","loc":{"start":53063,"end":53396,"line":{"s":884,"e":886,"code":["- Called from:","  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.","  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":0,"e":190}},"dim":["","list.168","listItem.1"],"code":"- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."},{"id":"/root/children/168/children/1/children/0","type":"paragraph","loc":{"start":53065,"end":53077,"line":{"s":884,"e":884,"code":["- Called from:"]},"column":{"s":2,"e":14}},"dim":["","list.168","listItem.1","paragraph.0"],"code":"Called from:"},{"id":"/root/children/168/children/1/children/0/children/0","type":"text","loc":{"start":53065,"end":53077,"line":{"s":884,"e":884,"code":["- Called from:"]},"column":{"s":2,"e":14}},"dim":["","list.168","listItem.1","paragraph.0","text.0"],"code":"Called from:"},{"id":"/root/children/168/children/1/children/1","type":"list","loc":{"start":53080,"end":53396,"line":{"s":885,"e":886,"code":["  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.","  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":2,"e":190}},"dim":["","list.168","listItem.1","list.1"],"code":"- `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.","symbName":"list","symbRange":[null,null],"symbRangeL":[885,886],"outerCode":"","outerHtml":""},{"id":"/root/children/168/children/1/children/1/children/0","type":"listItem","loc":{"start":53080,"end":53205,"line":{"s":885,"e":885,"code":["  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."]},"column":{"s":2,"e":127}},"dim":["","list.168","listItem.1","list.1","listItem.0"],"code":"- `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."},{"id":"/root/children/168/children/1/children/1/children/0/children/0","type":"paragraph","loc":{"start":53082,"end":53205,"line":{"s":885,"e":885,"code":["  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."]},"column":{"s":4,"e":127}},"dim":["","list.168","listItem.1","list.1","listItem.0","paragraph.0"],"code":"`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."},{"id":"/root/children/168/children/1/children/1/children/0/children/0/children/0","type":"inlineCode","loc":{"start":53082,"end":53108,"line":{"s":885,"e":885,"code":["  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."]},"column":{"s":4,"e":30}},"dim":["","list.168","listItem.1","list.1","listItem.0","paragraph.0","inlineCode.0"],"code":"`commands/cmdDashboard.js`"},{"id":"/root/children/168/children/1/children/1/children/0/children/0/children/1","type":"text","loc":{"start":53108,"end":53149,"line":{"s":885,"e":885,"code":["  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."]},"column":{"s":30,"e":71}},"dim":["","list.168","listItem.1","list.1","listItem.0","paragraph.0","text.1"],"code":" — dashboard \"matrix\" image, votingTypes "},{"id":"/root/children/168/children/1/children/1/children/0/children/0/children/2","type":"inlineCode","loc":{"start":53149,"end":53204,"line":{"s":885,"e":885,"code":["  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."]},"column":{"s":71,"e":126}},"dim":["","list.168","listItem.1","list.1","listItem.0","paragraph.0","inlineCode.2"],"code":"`{do:hot, schedule:pin, delegate:weak, eliminate:veto}`"},{"id":"/root/children/168/children/1/children/1/children/0/children/0/children/3","type":"text","loc":{"start":53204,"end":53205,"line":{"s":885,"e":885,"code":["  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`."]},"column":{"s":126,"e":127}},"dim":["","list.168","listItem.1","list.1","listItem.0","paragraph.0","text.3"],"code":"."},{"id":"/root/children/168/children/1/children/1/children/1","type":"listItem","loc":{"start":53208,"end":53396,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":2,"e":190}},"dim":["","list.168","listItem.1","list.1","listItem.1"],"code":"- `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."},{"id":"/root/children/168/children/1/children/1/children/1/children/0","type":"paragraph","loc":{"start":53210,"end":53396,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":4,"e":190}},"dim":["","list.168","listItem.1","list.1","listItem.1","paragraph.0"],"code":"`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."},{"id":"/root/children/168/children/1/children/1/children/1/children/0/children/0","type":"inlineCode","loc":{"start":53210,"end":53241,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":4,"e":35}},"dim":["","list.168","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.0"],"code":"`commands/cmdVotingCampaign.js`"},{"id":"/root/children/168/children/1/children/1/children/1/children/0/children/1","type":"text","loc":{"start":53241,"end":53275,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":35,"e":69}},"dim":["","list.168","listItem.1","list.1","listItem.1","paragraph.0","text.1"],"code":" — live voting campaign; the Vega "},{"id":"/root/children/168/children/1/children/1/children/1/children/0/children/2","type":"inlineCode","loc":{"start":53275,"end":53288,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":69,"e":82}},"dim":["","list.168","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.2"],"code":"`clickedWord`"},{"id":"/root/children/168/children/1/children/1/children/1/children/0/children/3","type":"text","loc":{"start":53288,"end":53310,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":82,"e":104}},"dim":["","list.168","listItem.1","list.1","listItem.1","paragraph.0","text.3"],"code":" signal handler calls "},{"id":"/root/children/168/children/1/children/1/children/1/children/0/children/4","type":"inlineCode","loc":{"start":53310,"end":53331,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":104,"e":125}},"dim":["","list.168","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.4"],"code":"`window.touchbarShow`"},{"id":"/root/children/168/children/1/children/1/children/1/children/0/children/5","type":"text","loc":{"start":53331,"end":53396,"line":{"s":886,"e":886,"code":["  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu."]},"column":{"s":125,"e":190}},"dim":["","list.168","listItem.1","list.1","listItem.1","paragraph.0","text.5"],"code":" at the clicked task's position to open the per-vote action menu."},{"id":"/root/children/169","type":"heading","loc":{"start":53398,"end":53417,"line":{"s":888,"e":888,"code":["## Touchbar Actions"]},"column":{"s":0,"e":19}},"dim":["","heading.169"],"code":"## Touchbar Actions","symbName":"heading","symbRange":[53419,53706],"symbRangeL":[888,894],"outerCode":"\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |","outerHtml":"\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>"},{"id":"/root/children/169/children/0","type":"text","loc":{"start":53401,"end":53417,"line":{"s":888,"e":888,"code":["## Touchbar Actions"]},"column":{"s":3,"e":19}},"dim":["","heading.169","text.0"],"code":"Touchbar Actions"},{"id":"/root/children/170","type":"paragraph","loc":{"start":53419,"end":53706,"line":{"s":890,"e":893,"code":["| Button                    | Click                  | Double-touch   |","| ------------------------- | ---------------------- | -------------- |","| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |","| (other buttons)           | primary action         | `action_extra` |"]},"column":{"s":0,"e":71}},"dim":["","paragraph.170"],"code":"| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |"},{"id":"/root/children/170/children/0","type":"text","loc":{"start":53419,"end":53593,"line":{"s":890,"e":892,"code":["| Button                    | Click                  | Double-touch   |","| ------------------------- | ---------------------- | -------------- |","| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |"]},"column":{"s":0,"e":30}},"dim":["","paragraph.170","text.0"],"code":"| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | "},{"id":"/root/children/170/children/1","type":"inlineCode","loc":{"start":53593,"end":53615,"line":{"s":892,"e":892,"code":["| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |"]},"column":{"s":30,"e":52}},"dim":["","paragraph.170","inlineCode.1"],"code":"`editor:duplicateLine`"},{"id":"/root/children/170/children/2","type":"text","loc":{"start":53615,"end":53618,"line":{"s":892,"e":892,"code":["| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |"]},"column":{"s":52,"e":55}},"dim":["","paragraph.170","text.2"],"code":" | "},{"id":"/root/children/170/children/3","type":"inlineCode","loc":{"start":53618,"end":53632,"line":{"s":892,"e":892,"code":["| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |"]},"column":{"s":55,"e":69}},"dim":["","paragraph.170","inlineCode.3"],"code":"`editor:paste`"},{"id":"/root/children/170/children/4","type":"text","loc":{"start":53632,"end":53690,"line":{"s":892,"e":893,"code":["| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |","| (other buttons)           | primary action         | `action_extra` |"]},"column":{"s":69,"e":55}},"dim":["","paragraph.170","text.4"],"code":" |\n| (other buttons)           | primary action         | "},{"id":"/root/children/170/children/5","type":"inlineCode","loc":{"start":53690,"end":53704,"line":{"s":893,"e":893,"code":["| (other buttons)           | primary action         | `action_extra` |"]},"column":{"s":55,"e":69}},"dim":["","paragraph.170","inlineCode.5"],"code":"`action_extra`"},{"id":"/root/children/170/children/6","type":"text","loc":{"start":53704,"end":53706,"line":{"s":893,"e":893,"code":["| (other buttons)           | primary action         | `action_extra` |"]},"column":{"s":69,"e":71}},"dim":["","paragraph.170","text.6"],"code":" |"},{"id":"/root/children/171","type":"heading","loc":{"start":53708,"end":53724,"line":{"s":895,"e":895,"code":["## Files Changed"]},"column":{"s":0,"e":16}},"dim":["","heading.171"],"code":"## Files Changed","symbName":"heading","symbRange":[53726,54002],"symbRangeL":[895,903],"outerCode":"\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---","outerHtml":"\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>"},{"id":"/root/children/171/children/0","type":"text","loc":{"start":53711,"end":53724,"line":{"s":895,"e":895,"code":["## Files Changed"]},"column":{"s":3,"e":16}},"dim":["","heading.171","text.0"],"code":"Files Changed"},{"id":"/root/children/172","type":"list","loc":{"start":53726,"end":53997,"line":{"s":897,"e":900,"code":["- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern","- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`","- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`","- `codeditor/src/databox.js`: mobile paste detection"]},"column":{"s":0,"e":52}},"dim":["","list.172"],"code":"- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection","symbName":"list","symbRange":[53999,54441],"symbRangeL":[897,922],"outerCode":"- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing\n\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.\n\n## Key conventions","outerHtml":"<ul><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>\n\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>\n\n<h2>Key conventions</h2>"},{"id":"/root/children/172/children/0","type":"listItem","loc":{"start":53726,"end":53802,"line":{"s":897,"e":897,"code":["- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern"]},"column":{"s":0,"e":76}},"dim":["","list.172","listItem.0"],"code":"- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern"},{"id":"/root/children/172/children/0/children/0","type":"paragraph","loc":{"start":53728,"end":53802,"line":{"s":897,"e":897,"code":["- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern"]},"column":{"s":2,"e":76}},"dim":["","list.172","listItem.0","paragraph.0"],"code":"`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern"},{"id":"/root/children/172/children/0/children/0/children/0","type":"inlineCode","loc":{"start":53728,"end":53744,"line":{"s":897,"e":897,"code":["- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern"]},"column":{"s":2,"e":18}},"dim":["","list.172","listItem.0","paragraph.0","inlineCode.0"],"code":"`ui-touchbar.js`"},{"id":"/root/children/172/children/0/children/0/children/1","type":"text","loc":{"start":53744,"end":53802,"line":{"s":897,"e":897,"code":["- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern"]},"column":{"s":18,"e":76}},"dim":["","list.172","listItem.0","paragraph.0","text.1"],"code":": Alpine init, native click delegation, double-tap pattern"},{"id":"/root/children/172/children/1","type":"listItem","loc":{"start":53803,"end":53871,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":0,"e":68}},"dim":["","list.172","listItem.1"],"code":"- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"},{"id":"/root/children/172/children/1/children/0","type":"paragraph","loc":{"start":53805,"end":53871,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":2,"e":68}},"dim":["","list.172","listItem.1","paragraph.0"],"code":"`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"},{"id":"/root/children/172/children/1/children/0/children/0","type":"inlineCode","loc":{"start":53805,"end":53826,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":2,"e":23}},"dim":["","list.172","listItem.1","paragraph.0","inlineCode.0"],"code":"`ui-touchbar-defs.js`"},{"id":"/root/children/172/children/1/children/0/children/1","type":"text","loc":{"start":53826,"end":53828,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":23,"e":25}},"dim":["","list.172","listItem.1","paragraph.0","text.1"],"code":": "},{"id":"/root/children/172/children/1/children/0/children/2","type":"inlineCode","loc":{"start":53828,"end":53835,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":25,"e":32}},"dim":["","list.172","listItem.1","paragraph.0","inlineCode.2"],"code":"`paste`"},{"id":"/root/children/172/children/1/children/0/children/3","type":"text","loc":{"start":53835,"end":53848,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":32,"e":45}},"dim":["","list.172","listItem.1","paragraph.0","text.3"],"code":" button with "},{"id":"/root/children/172/children/1/children/0/children/4","type":"inlineCode","loc":{"start":53848,"end":53856,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":45,"e":53}},"dim":["","list.172","listItem.1","paragraph.0","inlineCode.4"],"code":"`action`"},{"id":"/root/children/172/children/1/children/0/children/5","type":"text","loc":{"start":53856,"end":53857,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":53,"e":54}},"dim":["","list.172","listItem.1","paragraph.0","text.5"],"code":"/"},{"id":"/root/children/172/children/1/children/0/children/6","type":"inlineCode","loc":{"start":53857,"end":53871,"line":{"s":898,"e":898,"code":["- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`"]},"column":{"s":54,"e":68}},"dim":["","list.172","listItem.1","paragraph.0","inlineCode.6"],"code":"`action_extra`"},{"id":"/root/children/172/children/2","type":"listItem","loc":{"start":53872,"end":53944,"line":{"s":899,"e":899,"code":["- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"]},"column":{"s":0,"e":72}},"dim":["","list.172","listItem.2"],"code":"- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"},{"id":"/root/children/172/children/2/children/0","type":"paragraph","loc":{"start":53874,"end":53944,"line":{"s":899,"e":899,"code":["- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"]},"column":{"s":2,"e":72}},"dim":["","list.172","listItem.2","paragraph.0"],"code":"`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"},{"id":"/root/children/172/children/2/children/0/children/0","type":"inlineCode","loc":{"start":53874,"end":53899,"line":{"s":899,"e":899,"code":["- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"]},"column":{"s":2,"e":27}},"dim":["","list.172","listItem.2","paragraph.0","inlineCode.0"],"code":"`player/player-tweaks.js`"},{"id":"/root/children/172/children/2/children/0/children/1","type":"text","loc":{"start":53899,"end":53906,"line":{"s":899,"e":899,"code":["- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"]},"column":{"s":27,"e":34}},"dim":["","list.172","listItem.2","paragraph.0","text.1"],"code":": pass "},{"id":"/root/children/172/children/2/children/0/children/2","type":"inlineCode","loc":{"start":53906,"end":53914,"line":{"s":899,"e":899,"code":["- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"]},"column":{"s":34,"e":42}},"dim":["","list.172","listItem.2","paragraph.0","inlineCode.2"],"code":"`origin`"},{"id":"/root/children/172/children/2/children/0/children/3","type":"text","loc":{"start":53914,"end":53926,"line":{"s":899,"e":899,"code":["- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"]},"column":{"s":42,"e":54}},"dim":["","list.172","listItem.2","paragraph.0","text.3"],"code":" element to "},{"id":"/root/children/172/children/2/children/0/children/4","type":"inlineCode","loc":{"start":53926,"end":53944,"line":{"s":899,"e":899,"code":["- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`"]},"column":{"s":54,"e":72}},"dim":["","list.172","listItem.2","paragraph.0","inlineCode.4"],"code":"`touchbarDispatch`"},{"id":"/root/children/172/children/3","type":"listItem","loc":{"start":53945,"end":53997,"line":{"s":900,"e":900,"code":["- `codeditor/src/databox.js`: mobile paste detection"]},"column":{"s":0,"e":52}},"dim":["","list.172","listItem.3"],"code":"- `codeditor/src/databox.js`: mobile paste detection"},{"id":"/root/children/172/children/3/children/0","type":"paragraph","loc":{"start":53947,"end":53997,"line":{"s":900,"e":900,"code":["- `codeditor/src/databox.js`: mobile paste detection"]},"column":{"s":2,"e":52}},"dim":["","list.172","listItem.3","paragraph.0"],"code":"`codeditor/src/databox.js`: mobile paste detection"},{"id":"/root/children/172/children/3/children/0/children/0","type":"inlineCode","loc":{"start":53947,"end":53973,"line":{"s":900,"e":900,"code":["- `codeditor/src/databox.js`: mobile paste detection"]},"column":{"s":2,"e":28}},"dim":["","list.172","listItem.3","paragraph.0","inlineCode.0"],"code":"`codeditor/src/databox.js`"},{"id":"/root/children/172/children/3/children/0/children/1","type":"text","loc":{"start":53973,"end":53997,"line":{"s":900,"e":900,"code":["- `codeditor/src/databox.js`: mobile paste detection"]},"column":{"s":28,"e":52}},"dim":["","list.172","listItem.3","paragraph.0","text.1"],"code":": mobile paste detection"},{"id":"/root/children/173","type":"thematicBreak","loc":{"start":53999,"end":54002,"line":{"s":902,"e":902,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.173"],"code":"---"},{"id":"/root/children/174","type":"heading","loc":{"start":54004,"end":54014,"line":{"s":904,"e":904,"code":["## Testing"]},"column":{"s":0,"e":10}},"dim":["","heading.174"],"code":"## Testing","symbName":"heading","symbRange":[54016,54421],"symbRangeL":[904,920],"outerCode":"\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.","outerHtml":"\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>"},{"id":"/root/children/174/children/0","type":"text","loc":{"start":54007,"end":54014,"line":{"s":904,"e":904,"code":["## Testing"]},"column":{"s":3,"e":10}},"dim":["","heading.174","text.0"],"code":"Testing"},{"id":"/root/children/175","type":"code","loc":{"start":54016,"end":54201,"line":{"s":906,"e":915,"code":["```sh","# Unit/snapshot tests (Jest + ESM)","npm test","","# E2E (Playwright)","cd e2e","npm test                 # headless","npm run test:headed      # headed","npm run test:ui          # ui mode","```"]},"column":{"s":0,"e":3}},"dim":["","code.175"],"code":"```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```","symbName":"code","symbRange":[54203,63501],"symbRangeL":[null,1038],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done\n\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done\n\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps\n\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done\n\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done\n\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use\n\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed\n\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing\n\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.\n\n## Key conventions\n\n- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured\n\n## Voting system\n\n- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.\n\n## Touchbar system API\n\n- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show\n- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.\n\n## Architecture quirks\n\n- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).\n- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).\n\n## Heading-changes / Fragment management\n\n### Files changed\n\n- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`\n- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched\n\n### Dialog rename\n\n`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.\n\n### hasMetaData filter (heading report)\n\n`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.\n\n`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.\n\nDelete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.\n\n### fragmentUrl system\n\nFormat: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).\n\n**Functions:**\n\n- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)\n- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.\n\n### replantRef touchbar action\n\nTriggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:\n\n1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)\n2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`\n\nKey: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.\n\n### URL construction\n\nFollowing `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).\n\n### SQL rows\n\n`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.\n\n## OuchDB (Remote CouchDB/PouchDB)\n\n**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.\n\n**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>\n\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>\n\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>\n\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>\n\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>\n\n<h2>Key conventions</h2>\n\n<ul><li><b>ES modules</b> (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS</li><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>\n\n<h2>Voting system</h2>\n\n<ul><li>SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)</li><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.</li><li><b>Vote helper</b>: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.</li><li><b>Fragment ref helper</b>: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).</li><li><b>JSON options loader</b>: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.</li><li><b>Dialog-campaignes</b>: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.</li><li><b>After voting</b>, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.</li></ul>\n\n<h2>Touchbar system API</h2>\n\n<ul><li>`window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show</li><li>`window.touchbarDispatch(action, element)` — button click dispatch</li><li>`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap</li><li>`data-tooltip` — attribute for click/touch triggered toolbar</li><li>Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)</li><li>`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types</li><li><b>resultAction dispatch</b> (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.</li></ul>\n\n<h2>Architecture quirks</h2>\n\n<ul><li>`window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).</li><li>`proxy.STATE` is the mutable global state object (passed by reference).</li><li>Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.</li><li>Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.</li><li>Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.</li><li>`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.</li><li><b>`echoOut`</b> (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).</li></ul>\n\n<h2>Heading-changes / Fragment management</h2>\n\n<h3>Files changed</h3>\n\n<ul><li>`player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`</li><li>`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter</li><li>`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched</li></ul>\n\n<h3>Dialog rename</h3>\n\n<p>`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.</p>\n\n<h3>hasMetaData filter (heading report)</h3>\n\n<p>`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.</p>\n\n<p>`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.</p>\n\n<p>Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.</p>\n\n<h3>fragmentUrl system</h3>\n\n<p>Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).</p>\n\n<p><b>Functions:</b></p>\n\n<ul><li>`validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)</li><li>`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:<ol><li>Validates fragment URL format</li><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li><li>Returns `{ text, pathInRepo, startLine, endLine }` or `null`</li></ol></li></ul>\n\n<ul><li>`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.</li></ul>\n\n<h3>replantRef touchbar action</h3>\n\n<p>Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:</p>\n\n<ol><li>Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)</li><li>Checks `route.isInEditor` — only works in edit layer</li><li>Validates via `validateFragmentUrl(t)`</li><li>Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`</li><li>Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line</li><li>Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor</li><li>Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`</li></ol>\n\n<p>Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.</p>\n\n<h3>URL construction</h3>\n\n<p>Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).</p>\n\n<h3>SQL rows</h3>\n\n<p>`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.</p>\n\n<h2>OuchDB (Remote CouchDB/PouchDB)</h2>\n\n<p><b>Adapter:</b> `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.</p>\n\n<p><b>Wiring:</b> `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:</p>"},{"id":"/root/children/176","type":"paragraph","loc":{"start":54203,"end":54421,"line":{"s":917,"e":919,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).","Snapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.","Snapshot dir: `source/repolero-prototype/test/__snapshots__/`."]},"column":{"s":0,"e":62}},"dim":["","paragraph.176"],"code":"Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`."},{"id":"/root/children/176/children/0","type":"text","loc":{"start":54203,"end":54217,"line":{"s":917,"e":917,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script)."]},"column":{"s":0,"e":14}},"dim":["","paragraph.176","text.0"],"code":"Jest requires "},{"id":"/root/children/176/children/1","type":"inlineCode","loc":{"start":54217,"end":54244,"line":{"s":917,"e":917,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script)."]},"column":{"s":14,"e":41}},"dim":["","paragraph.176","inlineCode.1"],"code":"`--experimental-vm-modules`"},{"id":"/root/children/176/children/2","type":"text","loc":{"start":54244,"end":54257,"line":{"s":917,"e":917,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script)."]},"column":{"s":41,"e":54}},"dim":["","paragraph.176","text.2"],"code":" (already in "},{"id":"/root/children/176/children/3","type":"inlineCode","loc":{"start":54257,"end":54271,"line":{"s":917,"e":917,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script)."]},"column":{"s":54,"e":68}},"dim":["","paragraph.176","inlineCode.3"],"code":"`package.json`"},{"id":"/root/children/176/children/4","type":"text","loc":{"start":54271,"end":54272,"line":{"s":917,"e":917,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script)."]},"column":{"s":68,"e":69}},"dim":["","paragraph.176","text.4"],"code":" "},{"id":"/root/children/176/children/5","type":"inlineCode","loc":{"start":54272,"end":54278,"line":{"s":917,"e":917,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script)."]},"column":{"s":69,"e":75}},"dim":["","paragraph.176","inlineCode.5"],"code":"`test`"},{"id":"/root/children/176/children/6","type":"text","loc":{"start":54278,"end":54303,"line":{"s":917,"e":918,"code":["Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).","Snapshot file: `source/repolero-prototype/test/glass-search.test.mjs`."]},"column":{"s":75,"e":15}},"dim":["","paragraph.176","text.6"],"code":" script).\nSnapshot file: "},{"id":"/root/children/176/children/7","type":"inlineCode","loc":{"start":54303,"end":54357,"line":{"s":918,"e":918,"code":["Snapshot file: `source/repolero-prototype/test/glass-search.test.mjs`."]},"column":{"s":15,"e":69}},"dim":["","paragraph.176","inlineCode.7"],"code":"`source/repolero-prototype/test/glass-search.test.mjs`"},{"id":"/root/children/176/children/8","type":"text","loc":{"start":54357,"end":54373,"line":{"s":918,"e":919,"code":["Snapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.","Snapshot dir: `source/repolero-prototype/test/__snapshots__/`."]},"column":{"s":69,"e":14}},"dim":["","paragraph.176","text.8"],"code":".\nSnapshot dir: "},{"id":"/root/children/176/children/9","type":"inlineCode","loc":{"start":54373,"end":54420,"line":{"s":919,"e":919,"code":["Snapshot dir: `source/repolero-prototype/test/__snapshots__/`."]},"column":{"s":14,"e":61}},"dim":["","paragraph.176","inlineCode.9"],"code":"`source/repolero-prototype/test/__snapshots__/`"},{"id":"/root/children/176/children/10","type":"text","loc":{"start":54420,"end":54421,"line":{"s":919,"e":919,"code":["Snapshot dir: `source/repolero-prototype/test/__snapshots__/`."]},"column":{"s":61,"e":62}},"dim":["","paragraph.176","text.10"],"code":"."},{"id":"/root/children/177","type":"heading","loc":{"start":54423,"end":54441,"line":{"s":921,"e":921,"code":["## Key conventions"]},"column":{"s":0,"e":18}},"dim":["","heading.177"],"code":"## Key conventions","symbName":"heading","symbRange":[54443,54677],"symbRangeL":[921,927],"outerCode":"\n- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured","outerHtml":"\n<ul><li><b>ES modules</b> (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS</li><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>"},{"id":"/root/children/177/children/0","type":"text","loc":{"start":54426,"end":54441,"line":{"s":921,"e":921,"code":["## Key conventions"]},"column":{"s":3,"e":18}},"dim":["","heading.177","text.0"],"code":"Key conventions"},{"id":"/root/children/178","type":"list","loc":{"start":54443,"end":54677,"line":{"s":923,"e":926,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS","- **No TypeScript** — pure JS with JSDoc","- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)","- **No linter or typechecker** configured"]},"column":{"s":0,"e":41}},"dim":["","list.178"],"code":"- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured","symbName":"list","symbRange":[54679,54695],"symbRangeL":[923,929],"outerCode":"- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured\n\n## Voting system","outerHtml":"<ul><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>\n\n<h2>Voting system</h2>"},{"id":"/root/children/178/children/0","type":"listItem","loc":{"start":54443,"end":54517,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":0,"e":74}},"dim":["","list.178","listItem.0"],"code":"- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"},{"id":"/root/children/178/children/0/children/0","type":"paragraph","loc":{"start":54445,"end":54517,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":2,"e":74}},"dim":["","list.178","listItem.0","paragraph.0"],"code":"**ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"},{"id":"/root/children/178/children/0/children/0/children/0","type":"strong","loc":{"start":54445,"end":54459,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":2,"e":16}},"dim":["","list.178","listItem.0","paragraph.0","strong.0"],"code":"**ES modules**"},{"id":"/root/children/178/children/0/children/0/children/0/children/0","type":"text","loc":{"start":54447,"end":54457,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":4,"e":14}},"dim":["","list.178","listItem.0","paragraph.0","strong.0","text.0"],"code":"ES modules"},{"id":"/root/children/178/children/0/children/0/children/1","type":"text","loc":{"start":54459,"end":54461,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":16,"e":18}},"dim":["","list.178","listItem.0","paragraph.0","text.1"],"code":" ("},{"id":"/root/children/178/children/0/children/0/children/2","type":"inlineCode","loc":{"start":54461,"end":54479,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":18,"e":36}},"dim":["","list.178","listItem.0","paragraph.0","inlineCode.2"],"code":"`\"type\": \"module\"`"},{"id":"/root/children/178/children/0/children/0/children/3","type":"text","loc":{"start":54479,"end":54487,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":36,"e":44}},"dim":["","list.178","listItem.0","paragraph.0","text.3"],"code":") — use "},{"id":"/root/children/178/children/0/children/0/children/4","type":"inlineCode","loc":{"start":54487,"end":54495,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":44,"e":52}},"dim":["","list.178","listItem.0","paragraph.0","inlineCode.4"],"code":"`import`"},{"id":"/root/children/178/children/0/children/0/children/5","type":"text","loc":{"start":54495,"end":54496,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":52,"e":53}},"dim":["","list.178","listItem.0","paragraph.0","text.5"],"code":"/"},{"id":"/root/children/178/children/0/children/0/children/6","type":"inlineCode","loc":{"start":54496,"end":54504,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":53,"e":61}},"dim":["","list.178","listItem.0","paragraph.0","inlineCode.6"],"code":"`export`"},{"id":"/root/children/178/children/0/children/0/children/7","type":"text","loc":{"start":54504,"end":54517,"line":{"s":923,"e":923,"code":["- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS"]},"column":{"s":61,"e":74}},"dim":["","list.178","listItem.0","paragraph.0","text.7"],"code":", no CommonJS"},{"id":"/root/children/178/children/1","type":"listItem","loc":{"start":54518,"end":54558,"line":{"s":924,"e":924,"code":["- **No TypeScript** — pure JS with JSDoc"]},"column":{"s":0,"e":40}},"dim":["","list.178","listItem.1"],"code":"- **No TypeScript** — pure JS with JSDoc"},{"id":"/root/children/178/children/1/children/0","type":"paragraph","loc":{"start":54520,"end":54558,"line":{"s":924,"e":924,"code":["- **No TypeScript** — pure JS with JSDoc"]},"column":{"s":2,"e":40}},"dim":["","list.178","listItem.1","paragraph.0"],"code":"**No TypeScript** — pure JS with JSDoc"},{"id":"/root/children/178/children/1/children/0/children/0","type":"strong","loc":{"start":54520,"end":54537,"line":{"s":924,"e":924,"code":["- **No TypeScript** — pure JS with JSDoc"]},"column":{"s":2,"e":19}},"dim":["","list.178","listItem.1","paragraph.0","strong.0"],"code":"**No TypeScript**"},{"id":"/root/children/178/children/1/children/0/children/0/children/0","type":"text","loc":{"start":54522,"end":54535,"line":{"s":924,"e":924,"code":["- **No TypeScript** — pure JS with JSDoc"]},"column":{"s":4,"e":17}},"dim":["","list.178","listItem.1","paragraph.0","strong.0","text.0"],"code":"No TypeScript"},{"id":"/root/children/178/children/1/children/0/children/1","type":"text","loc":{"start":54537,"end":54558,"line":{"s":924,"e":924,"code":["- **No TypeScript** — pure JS with JSDoc"]},"column":{"s":19,"e":40}},"dim":["","list.178","listItem.1","paragraph.0","text.1"],"code":" — pure JS with JSDoc"},{"id":"/root/children/178/children/2","type":"listItem","loc":{"start":54559,"end":54635,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":0,"e":76}},"dim":["","list.178","listItem.2"],"code":"- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"},{"id":"/root/children/178/children/2/children/0","type":"paragraph","loc":{"start":54561,"end":54635,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":2,"e":76}},"dim":["","list.178","listItem.2","paragraph.0"],"code":"**Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"},{"id":"/root/children/178/children/2/children/0/children/0","type":"strong","loc":{"start":54561,"end":54573,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":2,"e":14}},"dim":["","list.178","listItem.2","paragraph.0","strong.0"],"code":"**Prettier**"},{"id":"/root/children/178/children/2/children/0/children/0/children/0","type":"text","loc":{"start":54563,"end":54571,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":4,"e":12}},"dim":["","list.178","listItem.2","paragraph.0","strong.0","text.0"],"code":"Prettier"},{"id":"/root/children/178/children/2/children/0/children/1","type":"text","loc":{"start":54573,"end":54575,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":14,"e":16}},"dim":["","list.178","listItem.2","paragraph.0","text.1"],"code":" ("},{"id":"/root/children/178/children/2/children/0/children/2","type":"inlineCode","loc":{"start":54575,"end":54589,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":16,"e":30}},"dim":["","list.178","listItem.2","paragraph.0","inlineCode.2"],"code":"`npx prettier`"},{"id":"/root/children/178/children/2/children/0/children/3","type":"text","loc":{"start":54589,"end":54596,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":30,"e":37}},"dim":["","list.178","listItem.2","paragraph.0","text.3"],"code":") with "},{"id":"/root/children/178/children/2/children/0/children/4","type":"inlineCode","loc":{"start":54596,"end":54609,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":37,"e":50}},"dim":["","list.178","listItem.2","paragraph.0","inlineCode.4"],"code":"`semi: false`"},{"id":"/root/children/178/children/2/children/0/children/5","type":"text","loc":{"start":54609,"end":54618,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":50,"e":59}},"dim":["","list.178","listItem.2","paragraph.0","text.5"],"code":" (except "},{"id":"/root/children/178/children/2/children/0/children/6","type":"inlineCode","loc":{"start":54618,"end":54628,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":59,"e":69}},"dim":["","list.178","listItem.2","paragraph.0","inlineCode.6"],"code":"`.test.js`"},{"id":"/root/children/178/children/2/children/0/children/7","type":"text","loc":{"start":54628,"end":54635,"line":{"s":925,"e":925,"code":["- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)"]},"column":{"s":69,"e":76}},"dim":["","list.178","listItem.2","paragraph.0","text.7"],"code":" files)"},{"id":"/root/children/178/children/3","type":"listItem","loc":{"start":54636,"end":54677,"line":{"s":926,"e":926,"code":["- **No linter or typechecker** configured"]},"column":{"s":0,"e":41}},"dim":["","list.178","listItem.3"],"code":"- **No linter or typechecker** configured"},{"id":"/root/children/178/children/3/children/0","type":"paragraph","loc":{"start":54638,"end":54677,"line":{"s":926,"e":926,"code":["- **No linter or typechecker** configured"]},"column":{"s":2,"e":41}},"dim":["","list.178","listItem.3","paragraph.0"],"code":"**No linter or typechecker** configured"},{"id":"/root/children/178/children/3/children/0/children/0","type":"strong","loc":{"start":54638,"end":54666,"line":{"s":926,"e":926,"code":["- **No linter or typechecker** configured"]},"column":{"s":2,"e":30}},"dim":["","list.178","listItem.3","paragraph.0","strong.0"],"code":"**No linter or typechecker**"},{"id":"/root/children/178/children/3/children/0/children/0/children/0","type":"text","loc":{"start":54640,"end":54664,"line":{"s":926,"e":926,"code":["- **No linter or typechecker** configured"]},"column":{"s":4,"e":28}},"dim":["","list.178","listItem.3","paragraph.0","strong.0","text.0"],"code":"No linter or typechecker"},{"id":"/root/children/178/children/3/children/0/children/1","type":"text","loc":{"start":54666,"end":54677,"line":{"s":926,"e":926,"code":["- **No linter or typechecker** configured"]},"column":{"s":30,"e":41}},"dim":["","list.178","listItem.3","paragraph.0","text.1"],"code":" configured"},{"id":"/root/children/179","type":"heading","loc":{"start":54679,"end":54695,"line":{"s":928,"e":928,"code":["## Voting system"]},"column":{"s":0,"e":16}},"dim":["","heading.179"],"code":"## Voting system","symbName":"heading","symbRange":[54697,57162],"symbRangeL":[928,949],"outerCode":"\n- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.","outerHtml":"\n<ul><li>SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)</li><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.</li><li><b>Vote helper</b>: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.</li><li><b>Fragment ref helper</b>: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).</li><li><b>JSON options loader</b>: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.</li><li><b>Dialog-campaignes</b>: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.</li><li><b>After voting</b>, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.</li></ul>"},{"id":"/root/children/179/children/0","type":"text","loc":{"start":54682,"end":54695,"line":{"s":928,"e":928,"code":["## Voting system"]},"column":{"s":3,"e":16}},"dim":["","heading.179","text.0"],"code":"Voting system"},{"id":"/root/children/180","type":"list","loc":{"start":54697,"end":57162,"line":{"s":930,"e":948,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)","- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)","  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`","  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them","- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)","- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`","  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)","  - **Do** strip `#/paper/` prefix from URIs","  - **Do** sanitize trail segments via `ssss.string.sanitizeName`","- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:","  ```js","  window.store(windowStoreKey(\"options/glass/activeCampaign\"));","  ```","  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.","- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.","- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).","- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.","- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.","- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":0,"e":213}},"dim":["","list.180"],"code":"- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.","symbName":"list","symbRange":[57164,57186],"symbRangeL":[930,951],"outerCode":"- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.\n\n## Touchbar system API","outerHtml":"<ul><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.</li><li><b>Vote helper</b>: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.</li><li><b>Fragment ref helper</b>: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).</li><li><b>JSON options loader</b>: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.</li><li><b>Dialog-campaignes</b>: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.</li><li><b>After voting</b>, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.</li></ul>\n\n<h2>Touchbar system API</h2>"},{"id":"/root/children/180/children/0","type":"listItem","loc":{"start":54697,"end":54756,"line":{"s":930,"e":930,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"]},"column":{"s":0,"e":59}},"dim":["","list.180","listItem.0"],"code":"- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"},{"id":"/root/children/180/children/0/children/0","type":"paragraph","loc":{"start":54699,"end":54756,"line":{"s":930,"e":930,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"]},"column":{"s":2,"e":59}},"dim":["","list.180","listItem.0","paragraph.0"],"code":"SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"},{"id":"/root/children/180/children/0/children/0/children/0","type":"text","loc":{"start":54699,"end":54716,"line":{"s":930,"e":930,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"]},"column":{"s":2,"e":19}},"dim":["","list.180","listItem.0","paragraph.0","text.0"],"code":"SQLite via HTTP ("},{"id":"/root/children/180/children/0/children/0/children/1","type":"inlineCode","loc":{"start":54716,"end":54729,"line":{"s":930,"e":930,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"]},"column":{"s":19,"e":32}},"dim":["","list.180","listItem.0","paragraph.0","inlineCode.1"],"code":"`sqlite-http`"},{"id":"/root/children/180/children/0/children/0/children/2","type":"text","loc":{"start":54729,"end":54737,"line":{"s":930,"e":930,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"]},"column":{"s":32,"e":40}},"dim":["","list.180","listItem.0","paragraph.0","text.2"],"code":" crate, "},{"id":"/root/children/180/children/0/children/0/children/3","type":"inlineCode","loc":{"start":54737,"end":54755,"line":{"s":930,"e":930,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"]},"column":{"s":40,"e":58}},"dim":["","list.180","listItem.0","paragraph.0","inlineCode.3"],"code":"`remote-sqlite.js`"},{"id":"/root/children/180/children/0/children/0/children/4","type":"text","loc":{"start":54755,"end":54756,"line":{"s":930,"e":930,"code":["- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)"]},"column":{"s":58,"e":59}},"dim":["","list.180","listItem.0","paragraph.0","text.4"],"code":")"},{"id":"/root/children/180/children/1","type":"listItem","loc":{"start":54757,"end":55030,"line":{"s":931,"e":933,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)","  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`","  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":0,"e":110}},"dim":["","list.180","listItem.1"],"code":"- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"},{"id":"/root/children/180/children/1/children/0","type":"paragraph","loc":{"start":54759,"end":54854,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":2,"e":97}},"dim":["","list.180","listItem.1","paragraph.0"],"code":"`STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"},{"id":"/root/children/180/children/1/children/0/children/0","type":"inlineCode","loc":{"start":54759,"end":54792,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":2,"e":35}},"dim":["","list.180","listItem.1","paragraph.0","inlineCode.0"],"code":"`STATE.remoteSqlite.execSql(sql)`"},{"id":"/root/children/180/children/1/children/0/children/1","type":"text","loc":{"start":54792,"end":54800,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":35,"e":43}},"dim":["","list.180","listItem.1","paragraph.0","text.1"],"code":" — uses "},{"id":"/root/children/180/children/1/children/0/children/2","type":"strong","loc":{"start":54800,"end":54824,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":43,"e":67}},"dim":["","list.180","listItem.1","paragraph.0","strong.2"],"code":"**string interpolation**"},{"id":"/root/children/180/children/1/children/0/children/2/children/0","type":"text","loc":{"start":54802,"end":54822,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":45,"e":65}},"dim":["","list.180","listItem.1","paragraph.0","strong.2","text.0"],"code":"string interpolation"},{"id":"/root/children/180/children/1/children/0/children/3","type":"text","loc":{"start":54824,"end":54837,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":67,"e":80}},"dim":["","list.180","listItem.1","paragraph.0","text.3"],"code":" in SQL (NOT "},{"id":"/root/children/180/children/1/children/0/children/4","type":"inlineCode","loc":{"start":54837,"end":54840,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":80,"e":83}},"dim":["","list.180","listItem.1","paragraph.0","inlineCode.4"],"code":"`?`"},{"id":"/root/children/180/children/1/children/0/children/5","type":"text","loc":{"start":54840,"end":54854,"line":{"s":931,"e":931,"code":["- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)"]},"column":{"s":83,"e":97}},"dim":["","list.180","listItem.1","paragraph.0","text.5"],"code":" placeholders)"},{"id":"/root/children/180/children/1/children/1","type":"list","loc":{"start":54857,"end":55030,"line":{"s":932,"e":933,"code":["  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`","  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":2,"e":110}},"dim":["","list.180","listItem.1","list.1"],"code":"- Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them","symbName":"list","symbRange":[null,null],"symbRangeL":[932,933],"outerCode":"","outerHtml":""},{"id":"/root/children/180/children/1/children/1/children/0","type":"listItem","loc":{"start":54857,"end":54919,"line":{"s":932,"e":932,"code":["  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"]},"column":{"s":2,"e":64}},"dim":["","list.180","listItem.1","list.1","listItem.0"],"code":"- Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"},{"id":"/root/children/180/children/1/children/1/children/0/children/0","type":"paragraph","loc":{"start":54859,"end":54919,"line":{"s":932,"e":932,"code":["  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"]},"column":{"s":4,"e":64}},"dim":["","list.180","listItem.1","list.1","listItem.0","paragraph.0"],"code":"Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"},{"id":"/root/children/180/children/1/children/1/children/0/children/0/children/0","type":"text","loc":{"start":54859,"end":54870,"line":{"s":932,"e":932,"code":["  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"]},"column":{"s":4,"e":15}},"dim":["","list.180","listItem.1","list.1","listItem.0","paragraph.0","text.0"],"code":"Reference: "},{"id":"/root/children/180/children/1/children/1/children/0/children/0/children/1","type":"inlineCode","loc":{"start":54870,"end":54895,"line":{"s":932,"e":932,"code":["  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"]},"column":{"s":15,"e":40}},"dim":["","list.180","listItem.1","list.1","listItem.0","paragraph.0","inlineCode.1"],"code":"`cmdVotingCampaign.js:90`"},{"id":"/root/children/180/children/1/children/1/children/0/children/0/children/2","type":"text","loc":{"start":54895,"end":54897,"line":{"s":932,"e":932,"code":["  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"]},"column":{"s":40,"e":42}},"dim":["","list.180","listItem.1","list.1","listItem.0","paragraph.0","text.2"],"code":", "},{"id":"/root/children/180/children/1/children/1/children/0/children/0/children/3","type":"inlineCode","loc":{"start":54897,"end":54919,"line":{"s":932,"e":932,"code":["  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`"]},"column":{"s":42,"e":64}},"dim":["","list.180","listItem.1","list.1","listItem.0","paragraph.0","inlineCode.3"],"code":"`player-paper.js:1020`"},{"id":"/root/children/180/children/1/children/1/children/1","type":"listItem","loc":{"start":54922,"end":55030,"line":{"s":933,"e":933,"code":["  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":2,"e":110}},"dim":["","list.180","listItem.1","list.1","listItem.1"],"code":"- Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"},{"id":"/root/children/180/children/1/children/1/children/1/children/0","type":"paragraph","loc":{"start":54924,"end":55030,"line":{"s":933,"e":933,"code":["  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":4,"e":110}},"dim":["","list.180","listItem.1","list.1","listItem.1","paragraph.0"],"code":"Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"},{"id":"/root/children/180/children/1/children/1/children/1/children/0/children/0","type":"text","loc":{"start":54924,"end":54949,"line":{"s":933,"e":933,"code":["  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":4,"e":29}},"dim":["","list.180","listItem.1","list.1","listItem.1","paragraph.0","text.0"],"code":"Do NOT use parameterized "},{"id":"/root/children/180/children/1/children/1/children/1/children/0/children/1","type":"inlineCode","loc":{"start":54949,"end":54952,"line":{"s":933,"e":933,"code":["  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":29,"e":32}},"dim":["","list.180","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.1"],"code":"`?`"},{"id":"/root/children/180/children/1/children/1/children/1/children/0/children/2","type":"text","loc":{"start":54952,"end":54988,"line":{"s":933,"e":933,"code":["  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":32,"e":68}},"dim":["","list.180","listItem.1","list.1","listItem.1","paragraph.0","text.2"],"code":" placeholders with args array — the "},{"id":"/root/children/180/children/1/children/1/children/1/children/0/children/3","type":"inlineCode","loc":{"start":54988,"end":55001,"line":{"s":933,"e":933,"code":["  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":68,"e":81}},"dim":["","list.180","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.3"],"code":"`sqlite-http`"},{"id":"/root/children/180/children/1/children/1/children/1/children/0/children/4","type":"text","loc":{"start":55001,"end":55030,"line":{"s":933,"e":933,"code":["  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them"]},"column":{"s":81,"e":110}},"dim":["","list.180","listItem.1","list.1","listItem.1","paragraph.0","text.4"],"code":" version doesn't support them"},{"id":"/root/children/180/children/2","type":"listItem","loc":{"start":55031,"end":55132,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":0,"e":101}},"dim":["","list.180","listItem.2"],"code":"- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"},{"id":"/root/children/180/children/2/children/0","type":"paragraph","loc":{"start":55033,"end":55132,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":2,"e":101}},"dim":["","list.180","listItem.2","paragraph.0"],"code":"Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"},{"id":"/root/children/180/children/2/children/0/children/0","type":"text","loc":{"start":55033,"end":55040,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":2,"e":9}},"dim":["","list.180","listItem.2","paragraph.0","text.0"],"code":"Table: "},{"id":"/root/children/180/children/2/children/0/children/1","type":"inlineCode","loc":{"start":55040,"end":55053,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":9,"e":22}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.1"],"code":"`repo_voting`"},{"id":"/root/children/180/children/2/children/0/children/2","type":"text","loc":{"start":55053,"end":55064,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":22,"e":33}},"dim":["","list.180","listItem.2","paragraph.0","text.2"],"code":" (columns: "},{"id":"/root/children/180/children/2/children/0/children/3","type":"inlineCode","loc":{"start":55064,"end":55067,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":33,"e":36}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.3"],"code":"`d`"},{"id":"/root/children/180/children/2/children/0/children/4","type":"text","loc":{"start":55067,"end":55069,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":36,"e":38}},"dim":["","list.180","listItem.2","paragraph.0","text.4"],"code":", "},{"id":"/root/children/180/children/2/children/0/children/5","type":"inlineCode","loc":{"start":55069,"end":55075,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":38,"e":44}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.5"],"code":"`user`"},{"id":"/root/children/180/children/2/children/0/children/6","type":"text","loc":{"start":55075,"end":55077,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":44,"e":46}},"dim":["","list.180","listItem.2","paragraph.0","text.6"],"code":", "},{"id":"/root/children/180/children/2/children/0/children/7","type":"inlineCode","loc":{"start":55077,"end":55083,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":46,"e":52}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.7"],"code":"`repo`"},{"id":"/root/children/180/children/2/children/0/children/8","type":"text","loc":{"start":55083,"end":55085,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":52,"e":54}},"dim":["","list.180","listItem.2","paragraph.0","text.8"],"code":", "},{"id":"/root/children/180/children/2/children/0/children/9","type":"inlineCode","loc":{"start":55085,"end":55095,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":54,"e":64}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.9"],"code":"`campaign`"},{"id":"/root/children/180/children/2/children/0/children/10","type":"text","loc":{"start":55095,"end":55097,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":64,"e":66}},"dim":["","list.180","listItem.2","paragraph.0","text.10"],"code":", "},{"id":"/root/children/180/children/2/children/0/children/11","type":"inlineCode","loc":{"start":55097,"end":55102,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":66,"e":71}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.11"],"code":"`ref`"},{"id":"/root/children/180/children/2/children/0/children/12","type":"text","loc":{"start":55102,"end":55104,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":71,"e":73}},"dim":["","list.180","listItem.2","paragraph.0","text.12"],"code":", "},{"id":"/root/children/180/children/2/children/0/children/13","type":"inlineCode","loc":{"start":55104,"end":55115,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":73,"e":84}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.13"],"code":"`operation`"},{"id":"/root/children/180/children/2/children/0/children/14","type":"text","loc":{"start":55115,"end":55117,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":84,"e":86}},"dim":["","list.180","listItem.2","paragraph.0","text.14"],"code":", "},{"id":"/root/children/180/children/2/children/0/children/15","type":"inlineCode","loc":{"start":55117,"end":55123,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":86,"e":92}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.15"],"code":"`num1`"},{"id":"/root/children/180/children/2/children/0/children/16","type":"text","loc":{"start":55123,"end":55125,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":92,"e":94}},"dim":["","list.180","listItem.2","paragraph.0","text.16"],"code":", "},{"id":"/root/children/180/children/2/children/0/children/17","type":"inlineCode","loc":{"start":55125,"end":55131,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":94,"e":100}},"dim":["","list.180","listItem.2","paragraph.0","inlineCode.17"],"code":"`num2`"},{"id":"/root/children/180/children/2/children/0/children/18","type":"text","loc":{"start":55131,"end":55132,"line":{"s":934,"e":934,"code":["- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)"]},"column":{"s":100,"e":101}},"dim":["","list.180","listItem.2","paragraph.0","text.18"],"code":")"},{"id":"/root/children/180/children/3","type":"listItem","loc":{"start":55133,"end":55457,"line":{"s":935,"e":938,"code":["- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`","  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)","  - **Do** strip `#/paper/` prefix from URIs","  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":0,"e":65}},"dim":["","list.180","listItem.3"],"code":"- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"},{"id":"/root/children/180/children/3/children/0","type":"paragraph","loc":{"start":55135,"end":55268,"line":{"s":935,"e":935,"code":["- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`"]},"column":{"s":2,"e":135}},"dim":["","list.180","listItem.3","paragraph.0"],"code":"`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`"},{"id":"/root/children/180/children/3/children/0/children/0","type":"inlineCode","loc":{"start":55135,"end":55148,"line":{"s":935,"e":935,"code":["- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`"]},"column":{"s":2,"e":15}},"dim":["","list.180","listItem.3","paragraph.0","inlineCode.0"],"code":"`fragmentRef`"},{"id":"/root/children/180/children/3/children/0/children/1","type":"text","loc":{"start":55148,"end":55167,"line":{"s":935,"e":935,"code":["- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`"]},"column":{"s":15,"e":34}},"dim":["","list.180","listItem.3","paragraph.0","text.1"],"code":" format must match "},{"id":"/root/children/180/children/3/children/0/children/2","type":"inlineCode","loc":{"start":55167,"end":55197,"line":{"s":935,"e":935,"code":["- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`"]},"column":{"s":34,"e":64}},"dim":["","list.180","listItem.3","paragraph.0","inlineCode.2"],"code":"`cache_ast_lake_nodes_ref.ref`"},{"id":"/root/children/180/children/3/children/0/children/3","type":"text","loc":{"start":55197,"end":55218,"line":{"s":935,"e":935,"code":["- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`"]},"column":{"s":64,"e":85}},"dim":["","list.180","listItem.3","paragraph.0","text.3"],"code":": relative path like "},{"id":"/root/children/180/children/3/children/0/children/4","type":"inlineCode","loc":{"start":55218,"end":55268,"line":{"s":935,"e":935,"code":["- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`"]},"column":{"s":85,"e":135}},"dim":["","list.180","listItem.3","paragraph.0","inlineCode.4"],"code":"`paper/filename.mdd:lineNum::sanitized/trail/path`"},{"id":"/root/children/180/children/3/children/1","type":"list","loc":{"start":55271,"end":55457,"line":{"s":936,"e":938,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)","  - **Do** strip `#/paper/` prefix from URIs","  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":2,"e":65}},"dim":["","list.180","listItem.3","list.1"],"code":"- **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`","symbName":"list","symbRange":[null,null],"symbRangeL":[936,938],"outerCode":"  - **Do** strip `#/paper/` prefix from URIs","outerHtml":"<ul><li><b>Do</b> strip `#/paper/` prefix from URIs</li></ul>"},{"id":"/root/children/180/children/3/children/1/children/0","type":"listItem","loc":{"start":55271,"end":55346,"line":{"s":936,"e":936,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"]},"column":{"s":2,"e":77}},"dim":["","list.180","listItem.3","list.1","listItem.0"],"code":"- **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"},{"id":"/root/children/180/children/3/children/1/children/0/children/0","type":"paragraph","loc":{"start":55273,"end":55346,"line":{"s":936,"e":936,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"]},"column":{"s":4,"e":77}},"dim":["","list.180","listItem.3","list.1","listItem.0","paragraph.0"],"code":"**Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"},{"id":"/root/children/180/children/3/children/1/children/0/children/0/children/0","type":"strong","loc":{"start":55273,"end":55283,"line":{"s":936,"e":936,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"]},"column":{"s":4,"e":14}},"dim":["","list.180","listItem.3","list.1","listItem.0","paragraph.0","strong.0"],"code":"**Do NOT**"},{"id":"/root/children/180/children/3/children/1/children/0/children/0/children/0/children/0","type":"text","loc":{"start":55275,"end":55281,"line":{"s":936,"e":936,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"]},"column":{"s":6,"e":12}},"dim":["","list.180","listItem.3","list.1","listItem.0","paragraph.0","strong.0","text.0"],"code":"Do NOT"},{"id":"/root/children/180/children/3/children/1/children/0/children/0/children/1","type":"text","loc":{"start":55283,"end":55317,"line":{"s":936,"e":936,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"]},"column":{"s":14,"e":48}},"dim":["","list.180","listItem.3","list.1","listItem.0","paragraph.0","text.1"],"code":" use full filesystem paths (e.g., "},{"id":"/root/children/180/children/3/children/1/children/0/children/0/children/2","type":"inlineCode","loc":{"start":55317,"end":55345,"line":{"s":936,"e":936,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"]},"column":{"s":48,"e":76}},"dim":["","list.180","listItem.3","list.1","listItem.0","paragraph.0","inlineCode.2"],"code":"`/home/looper/.../paper/...`"},{"id":"/root/children/180/children/3/children/1/children/0/children/0/children/3","type":"text","loc":{"start":55345,"end":55346,"line":{"s":936,"e":936,"code":["  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)"]},"column":{"s":76,"e":77}},"dim":["","list.180","listItem.3","list.1","listItem.0","paragraph.0","text.3"],"code":")"},{"id":"/root/children/180/children/3/children/1/children/1","type":"listItem","loc":{"start":55349,"end":55391,"line":{"s":937,"e":937,"code":["  - **Do** strip `#/paper/` prefix from URIs"]},"column":{"s":2,"e":44}},"dim":["","list.180","listItem.3","list.1","listItem.1"],"code":"- **Do** strip `#/paper/` prefix from URIs"},{"id":"/root/children/180/children/3/children/1/children/1/children/0","type":"paragraph","loc":{"start":55351,"end":55391,"line":{"s":937,"e":937,"code":["  - **Do** strip `#/paper/` prefix from URIs"]},"column":{"s":4,"e":44}},"dim":["","list.180","listItem.3","list.1","listItem.1","paragraph.0"],"code":"**Do** strip `#/paper/` prefix from URIs"},{"id":"/root/children/180/children/3/children/1/children/1/children/0/children/0","type":"strong","loc":{"start":55351,"end":55357,"line":{"s":937,"e":937,"code":["  - **Do** strip `#/paper/` prefix from URIs"]},"column":{"s":4,"e":10}},"dim":["","list.180","listItem.3","list.1","listItem.1","paragraph.0","strong.0"],"code":"**Do**"},{"id":"/root/children/180/children/3/children/1/children/1/children/0/children/0/children/0","type":"text","loc":{"start":55353,"end":55355,"line":{"s":937,"e":937,"code":["  - **Do** strip `#/paper/` prefix from URIs"]},"column":{"s":6,"e":8}},"dim":["","list.180","listItem.3","list.1","listItem.1","paragraph.0","strong.0","text.0"],"code":"Do"},{"id":"/root/children/180/children/3/children/1/children/1/children/0/children/1","type":"text","loc":{"start":55357,"end":55364,"line":{"s":937,"e":937,"code":["  - **Do** strip `#/paper/` prefix from URIs"]},"column":{"s":10,"e":17}},"dim":["","list.180","listItem.3","list.1","listItem.1","paragraph.0","text.1"],"code":" strip "},{"id":"/root/children/180/children/3/children/1/children/1/children/0/children/2","type":"inlineCode","loc":{"start":55364,"end":55374,"line":{"s":937,"e":937,"code":["  - **Do** strip `#/paper/` prefix from URIs"]},"column":{"s":17,"e":27}},"dim":["","list.180","listItem.3","list.1","listItem.1","paragraph.0","inlineCode.2"],"code":"`#/paper/`"},{"id":"/root/children/180/children/3/children/1/children/1/children/0/children/3","type":"text","loc":{"start":55374,"end":55391,"line":{"s":937,"e":937,"code":["  - **Do** strip `#/paper/` prefix from URIs"]},"column":{"s":27,"e":44}},"dim":["","list.180","listItem.3","list.1","listItem.1","paragraph.0","text.3"],"code":" prefix from URIs"},{"id":"/root/children/180/children/3/children/1/children/2","type":"listItem","loc":{"start":55394,"end":55457,"line":{"s":938,"e":938,"code":["  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":2,"e":65}},"dim":["","list.180","listItem.3","list.1","listItem.2"],"code":"- **Do** sanitize trail segments via `ssss.string.sanitizeName`"},{"id":"/root/children/180/children/3/children/1/children/2/children/0","type":"paragraph","loc":{"start":55396,"end":55457,"line":{"s":938,"e":938,"code":["  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":4,"e":65}},"dim":["","list.180","listItem.3","list.1","listItem.2","paragraph.0"],"code":"**Do** sanitize trail segments via `ssss.string.sanitizeName`"},{"id":"/root/children/180/children/3/children/1/children/2/children/0/children/0","type":"strong","loc":{"start":55396,"end":55402,"line":{"s":938,"e":938,"code":["  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":4,"e":10}},"dim":["","list.180","listItem.3","list.1","listItem.2","paragraph.0","strong.0"],"code":"**Do**"},{"id":"/root/children/180/children/3/children/1/children/2/children/0/children/0/children/0","type":"text","loc":{"start":55398,"end":55400,"line":{"s":938,"e":938,"code":["  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":6,"e":8}},"dim":["","list.180","listItem.3","list.1","listItem.2","paragraph.0","strong.0","text.0"],"code":"Do"},{"id":"/root/children/180/children/3/children/1/children/2/children/0/children/1","type":"text","loc":{"start":55402,"end":55431,"line":{"s":938,"e":938,"code":["  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":10,"e":39}},"dim":["","list.180","listItem.3","list.1","listItem.2","paragraph.0","text.1"],"code":" sanitize trail segments via "},{"id":"/root/children/180/children/3/children/1/children/2/children/0/children/2","type":"inlineCode","loc":{"start":55431,"end":55457,"line":{"s":938,"e":938,"code":["  - **Do** sanitize trail segments via `ssss.string.sanitizeName`"]},"column":{"s":39,"e":65}},"dim":["","list.180","listItem.3","list.1","listItem.2","paragraph.0","inlineCode.2"],"code":"`ssss.string.sanitizeName`"},{"id":"/root/children/180/children/4","type":"listItem","loc":{"start":55458,"end":55906,"line":{"s":939,"e":943,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:","  ```js","  window.store(windowStoreKey(\"options/glass/activeCampaign\"));","  ```","  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":0,"e":210}},"dim":["","list.180","listItem.4"],"code":"- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."},{"id":"/root/children/180/children/4/children/0","type":"paragraph","loc":{"start":55460,"end":55617,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":2,"e":159}},"dim":["","list.180","listItem.4","paragraph.0"],"code":"**Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"},{"id":"/root/children/180/children/4/children/0/children/0","type":"strong","loc":{"start":55460,"end":55479,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":2,"e":21}},"dim":["","list.180","listItem.4","paragraph.0","strong.0"],"code":"**Campaign prefix**"},{"id":"/root/children/180/children/4/children/0/children/0/children/0","type":"text","loc":{"start":55462,"end":55477,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":4,"e":19}},"dim":["","list.180","listItem.4","paragraph.0","strong.0","text.0"],"code":"Campaign prefix"},{"id":"/root/children/180/children/4/children/0/children/1","type":"text","loc":{"start":55479,"end":55481,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":21,"e":23}},"dim":["","list.180","listItem.4","paragraph.0","text.1"],"code":": "},{"id":"/root/children/180/children/4/children/0/children/2","type":"inlineCode","loc":{"start":55481,"end":55522,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":23,"e":64}},"dim":["","list.180","listItem.4","paragraph.0","inlineCode.2"],"code":"`<select id=\"activeCampaign\" persistent>`"},{"id":"/root/children/180/children/4/children/0/children/3","type":"text","loc":{"start":55522,"end":55526,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":64,"e":68}},"dim":["","list.180","listItem.4","paragraph.0","text.3"],"code":" in "},{"id":"/root/children/180/children/4/children/0/children/4","type":"inlineCode","loc":{"start":55526,"end":55544,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":68,"e":86}},"dim":["","list.180","listItem.4","paragraph.0","inlineCode.4"],"code":"`view.jq.html:368`"},{"id":"/root/children/180/children/4/children/0/children/5","type":"text","loc":{"start":55544,"end":55583,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":86,"e":125}},"dim":["","list.180","listItem.4","paragraph.0","text.5"],"code":" holds the active campaign scope (e.g. "},{"id":"/root/children/180/children/4/children/0/children/6","type":"inlineCode","loc":{"start":55583,"end":55595,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":125,"e":137}},"dim":["","list.180","listItem.4","paragraph.0","inlineCode.6"],"code":"`eisenhower`"},{"id":"/root/children/180/children/4/children/0/children/7","type":"text","loc":{"start":55595,"end":55597,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":137,"e":139}},"dim":["","list.180","listItem.4","paragraph.0","text.7"],"code":", "},{"id":"/root/children/180/children/4/children/0/children/8","type":"inlineCode","loc":{"start":55597,"end":55605,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":139,"e":147}},"dim":["","list.180","listItem.4","paragraph.0","inlineCode.8"],"code":"`debate`"},{"id":"/root/children/180/children/4/children/0/children/9","type":"text","loc":{"start":55605,"end":55617,"line":{"s":939,"e":939,"code":["- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:"]},"column":{"s":147,"e":159}},"dim":["","list.180","listItem.4","paragraph.0","text.9"],"code":"). Read via:"},{"id":"/root/children/180/children/4/children/1","type":"code","loc":{"start":55620,"end":55695,"line":{"s":940,"e":942,"code":["  ```js","  window.store(windowStoreKey(\"options/glass/activeCampaign\"));","  ```"]},"column":{"s":2,"e":5}},"dim":["","list.180","listItem.4","code.1"],"code":"```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```","symbName":"code","symbRange":[55698,null],"symbRangeL":[null,942],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done\n\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done\n\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps\n\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done\n\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done\n\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use\n\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed\n\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing\n\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.\n\n## Key conventions\n\n- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured\n\n## Voting system\n\n- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>\n\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>\n\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>\n\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>\n\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>\n\n<h2>Key conventions</h2>\n\n<ul><li><b>ES modules</b> (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS</li><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>\n\n<h2>Voting system</h2>\n\n<ul><li>SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)</li><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));</li></ul>"},{"id":"/root/children/180/children/4/children/2","type":"paragraph","loc":{"start":55698,"end":55906,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":2,"e":210}},"dim":["","list.180","listItem.4","paragraph.2"],"code":"The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."},{"id":"/root/children/180/children/4/children/2/children/0","type":"text","loc":{"start":55698,"end":55702,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":2,"e":6}},"dim":["","list.180","listItem.4","paragraph.2","text.0"],"code":"The "},{"id":"/root/children/180/children/4/children/2/children/1","type":"inlineCode","loc":{"start":55702,"end":55716,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":6,"e":20}},"dim":["","list.180","listItem.4","paragraph.2","inlineCode.1"],"code":"`[persistent]`"},{"id":"/root/children/180/children/4/children/2/children/2","type":"text","loc":{"start":55716,"end":55728,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":20,"e":32}},"dim":["","list.180","listItem.4","paragraph.2","text.2"],"code":" handler in "},{"id":"/root/children/180/children/4/children/2/children/3","type":"inlineCode","loc":{"start":55728,"end":55749,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":32,"e":53}},"dim":["","list.180","listItem.4","paragraph.2","inlineCode.3"],"code":"`player.js:1000-1046`"},{"id":"/root/children/180/children/4/children/2/children/4","type":"text","loc":{"start":55749,"end":55770,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":53,"e":74}},"dim":["","list.180","listItem.4","paragraph.2","text.4"],"code":" auto-saves/restores "},{"id":"/root/children/180/children/4/children/2/children/5","type":"inlineCode","loc":{"start":55770,"end":55778,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":74,"e":82}},"dim":["","list.180","listItem.4","paragraph.2","inlineCode.5"],"code":"`SELECT`"},{"id":"/root/children/180/children/4/children/2/children/6","type":"text","loc":{"start":55778,"end":55813,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":82,"e":117}},"dim":["","list.180","listItem.4","paragraph.2","text.6"],"code":" values to localStorage. A sync to "},{"id":"/root/children/180/children/4/children/2/children/7","type":"inlineCode","loc":{"start":55813,"end":55861,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":117,"e":165}},"dim":["","list.180","listItem.4","paragraph.2","inlineCode.7"],"code":"`windowStoreKey(\"options/glass/activeCampaign\")`"},{"id":"/root/children/180/children/4/children/2/children/8","type":"text","loc":{"start":55861,"end":55875,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":165,"e":179}},"dim":["","list.180","listItem.4","paragraph.2","text.8"],"code":" is added for "},{"id":"/root/children/180/children/4/children/2/children/9","type":"inlineCode","loc":{"start":55875,"end":55892,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":179,"e":196}},"dim":["","list.180","listItem.4","paragraph.2","inlineCode.9"],"code":"`#activeCampaign`"},{"id":"/root/children/180/children/4/children/2/children/10","type":"text","loc":{"start":55892,"end":55906,"line":{"s":943,"e":943,"code":["  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically."]},"column":{"s":196,"e":210}},"dim":["","list.180","listItem.4","paragraph.2","text.10"],"code":" specifically."},{"id":"/root/children/180/children/5","type":"listItem","loc":{"start":55907,"end":56100,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":0,"e":193}},"dim":["","list.180","listItem.5"],"code":"- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."},{"id":"/root/children/180/children/5/children/0","type":"paragraph","loc":{"start":55909,"end":56100,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":2,"e":193}},"dim":["","list.180","listItem.5","paragraph.0"],"code":"**Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."},{"id":"/root/children/180/children/5/children/0/children/0","type":"strong","loc":{"start":55909,"end":55924,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":2,"e":17}},"dim":["","list.180","listItem.5","paragraph.0","strong.0"],"code":"**Vote helper**"},{"id":"/root/children/180/children/5/children/0/children/0/children/0","type":"text","loc":{"start":55911,"end":55922,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":4,"e":15}},"dim":["","list.180","listItem.5","paragraph.0","strong.0","text.0"],"code":"Vote helper"},{"id":"/root/children/180/children/5/children/0/children/1","type":"text","loc":{"start":55924,"end":55926,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":17,"e":19}},"dim":["","list.180","listItem.5","paragraph.0","text.1"],"code":": "},{"id":"/root/children/180/children/5/children/0/children/2","type":"inlineCode","loc":{"start":55926,"end":55969,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":19,"e":62}},"dim":["","list.180","listItem.5","paragraph.0","inlineCode.2"],"code":"`voteRepo(STATE, campaign, ref, operation)`"},{"id":"/root/children/180/children/5/children/0/children/3","type":"text","loc":{"start":55969,"end":55973,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":62,"e":66}},"dim":["","list.180","listItem.5","paragraph.0","text.3"],"code":" in "},{"id":"/root/children/180/children/5/children/0/children/4","type":"inlineCode","loc":{"start":55973,"end":55994,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":66,"e":87}},"dim":["","list.180","listItem.5","paragraph.0","inlineCode.4"],"code":"`player-utils.js:643`"},{"id":"/root/children/180/children/5/children/0/children/5","type":"text","loc":{"start":55994,"end":56100,"line":{"s":944,"e":944,"code":["- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."]},"column":{"s":87,"e":193}},"dim":["","list.180","listItem.5","paragraph.0","text.5"],"code":" — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value."},{"id":"/root/children/180/children/6","type":"listItem","loc":{"start":56101,"end":56414,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":0,"e":313}},"dim":["","list.180","listItem.6"],"code":"- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."},{"id":"/root/children/180/children/6/children/0","type":"paragraph","loc":{"start":56103,"end":56414,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":2,"e":313}},"dim":["","list.180","listItem.6","paragraph.0"],"code":"**Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."},{"id":"/root/children/180/children/6/children/0/children/0","type":"strong","loc":{"start":56103,"end":56126,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":2,"e":25}},"dim":["","list.180","listItem.6","paragraph.0","strong.0"],"code":"**Fragment ref helper**"},{"id":"/root/children/180/children/6/children/0/children/0/children/0","type":"text","loc":{"start":56105,"end":56124,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":4,"e":23}},"dim":["","list.180","listItem.6","paragraph.0","strong.0","text.0"],"code":"Fragment ref helper"},{"id":"/root/children/180/children/6/children/0/children/1","type":"text","loc":{"start":56126,"end":56128,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":25,"e":27}},"dim":["","list.180","listItem.6","paragraph.0","text.1"],"code":": "},{"id":"/root/children/180/children/6/children/0/children/2","type":"inlineCode","loc":{"start":56128,"end":56177,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":27,"e":76}},"dim":["","list.180","listItem.6","paragraph.0","inlineCode.2"],"code":"`buildFragmentRef(basePath, trail, sanitizeName)`"},{"id":"/root/children/180/children/6/children/0/children/3","type":"text","loc":{"start":56177,"end":56181,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":76,"e":80}},"dim":["","list.180","listItem.6","paragraph.0","text.3"],"code":" in "},{"id":"/root/children/180/children/6/children/0/children/4","type":"inlineCode","loc":{"start":56181,"end":56202,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":80,"e":101}},"dim":["","list.180","listItem.6","paragraph.0","inlineCode.4"],"code":"`player-utils.js:635`"},{"id":"/root/children/180/children/6/children/0/children/5","type":"text","loc":{"start":56202,"end":56216,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":101,"e":115}},"dim":["","list.180","listItem.6","paragraph.0","text.5"],"code":" — constructs "},{"id":"/root/children/180/children/6/children/0/children/6","type":"inlineCode","loc":{"start":56216,"end":56239,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":115,"e":138}},"dim":["","list.180","listItem.6","paragraph.0","inlineCode.6"],"code":"`path::sanitized/trail`"},{"id":"/root/children/180/children/6/children/0/children/7","type":"text","loc":{"start":56239,"end":56269,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":138,"e":168}},"dim":["","list.180","listItem.6","paragraph.0","text.7"],"code":" from a path (strips existing "},{"id":"/root/children/180/children/6/children/0/children/8","type":"inlineCode","loc":{"start":56269,"end":56273,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":168,"e":172}},"dim":["","list.180","listItem.6","paragraph.0","inlineCode.8"],"code":"`::`"},{"id":"/root/children/180/children/6/children/0/children/9","type":"text","loc":{"start":56273,"end":56295,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":172,"e":194}},"dim":["","list.180","listItem.6","paragraph.0","text.9"],"code":"), accepting trail as "},{"id":"/root/children/180/children/6/children/0/children/10","type":"inlineCode","loc":{"start":56295,"end":56298,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":194,"e":197}},"dim":["","list.180","listItem.6","paragraph.0","inlineCode.10"],"code":"`;`"},{"id":"/root/children/180/children/6/children/0/children/11","type":"text","loc":{"start":56298,"end":56352,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":197,"e":251}},"dim":["","list.180","listItem.6","paragraph.0","text.11"],"code":"-delimited string or array. Used in both paper layer ("},{"id":"/root/children/180/children/6/children/0/children/12","type":"inlineCode","loc":{"start":56352,"end":56373,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":251,"e":272}},"dim":["","list.180","listItem.6","paragraph.0","inlineCode.12"],"code":"`player-utils.js:577`"},{"id":"/root/children/180/children/6/children/0/children/13","type":"text","loc":{"start":56373,"end":56392,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":272,"e":291}},"dim":["","list.180","listItem.6","paragraph.0","text.13"],"code":") and glass layer ("},{"id":"/root/children/180/children/6/children/0/children/14","type":"inlineCode","loc":{"start":56392,"end":56412,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":291,"e":311}},"dim":["","list.180","listItem.6","paragraph.0","inlineCode.14"],"code":"`player-glass.js:86`"},{"id":"/root/children/180/children/6/children/0/children/15","type":"text","loc":{"start":56412,"end":56414,"line":{"s":945,"e":945,"code":["- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`)."]},"column":{"s":311,"e":313}},"dim":["","list.180","listItem.6","paragraph.0","text.15"],"code":")."},{"id":"/root/children/180/children/7","type":"listItem","loc":{"start":56415,"end":56678,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":0,"e":263}},"dim":["","list.180","listItem.7"],"code":"- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."},{"id":"/root/children/180/children/7/children/0","type":"paragraph","loc":{"start":56417,"end":56678,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":2,"e":263}},"dim":["","list.180","listItem.7","paragraph.0"],"code":"**JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."},{"id":"/root/children/180/children/7/children/0/children/0","type":"strong","loc":{"start":56417,"end":56440,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":2,"e":25}},"dim":["","list.180","listItem.7","paragraph.0","strong.0"],"code":"**JSON options loader**"},{"id":"/root/children/180/children/7/children/0/children/0/children/0","type":"text","loc":{"start":56419,"end":56438,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":4,"e":23}},"dim":["","list.180","listItem.7","paragraph.0","strong.0","text.0"],"code":"JSON options loader"},{"id":"/root/children/180/children/7/children/0/children/1","type":"text","loc":{"start":56440,"end":56442,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":25,"e":27}},"dim":["","list.180","listItem.7","paragraph.0","text.1"],"code":": "},{"id":"/root/children/180/children/7/children/0/children/2","type":"inlineCode","loc":{"start":56442,"end":56474,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":27,"e":59}},"dim":["","list.180","listItem.7","paragraph.0","inlineCode.2"],"code":"`loadJsonOptions(url, storeKey)`"},{"id":"/root/children/180/children/7/children/0/children/3","type":"text","loc":{"start":56474,"end":56478,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":59,"e":63}},"dim":["","list.180","listItem.7","paragraph.0","text.3"],"code":" in "},{"id":"/root/children/180/children/7/children/0/children/4","type":"inlineCode","loc":{"start":56478,"end":56499,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":63,"e":84}},"dim":["","list.180","listItem.7","paragraph.0","inlineCode.4"],"code":"`player-utils.js:643`"},{"id":"/root/children/180/children/7/children/0/children/5","type":"text","loc":{"start":56499,"end":56520,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":84,"e":105}},"dim":["","list.180","listItem.7","paragraph.0","text.5"],"code":" — fetches JSON from "},{"id":"/root/children/180/children/7/children/0/children/6","type":"inlineCode","loc":{"start":56520,"end":56525,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":105,"e":110}},"dim":["","list.180","listItem.7","paragraph.0","inlineCode.6"],"code":"`url`"},{"id":"/root/children/180/children/7/children/0/children/7","type":"text","loc":{"start":56525,"end":56540,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":110,"e":125}},"dim":["","list.180","listItem.7","paragraph.0","text.7"],"code":" and stores on "},{"id":"/root/children/180/children/7/children/0/children/8","type":"inlineCode","loc":{"start":56540,"end":56573,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":125,"e":158}},"dim":["","list.180","listItem.7","paragraph.0","inlineCode.8"],"code":"`Alpine.store(\"glass\")[storeKey]`"},{"id":"/root/children/180/children/7/children/0/children/9","type":"text","loc":{"start":56573,"end":56614,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":158,"e":199}},"dim":["","list.180","listItem.7","paragraph.0","text.9"],"code":". Used for dynamic campaign options from "},{"id":"/root/children/180/children/7/children/0/children/10","type":"inlineCode","loc":{"start":56614,"end":56632,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":199,"e":217}},"dim":["","list.180","listItem.7","paragraph.0","inlineCode.10"],"code":"`campaignsJsonUrl`"},{"id":"/root/children/180/children/7/children/0/children/11","type":"text","loc":{"start":56632,"end":56678,"line":{"s":946,"e":946,"code":["- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options."]},"column":{"s":217,"e":263}},"dim":["","list.180","listItem.7","paragraph.0","text.11"],"code":". General-purpose for any JSON-loaded options."},{"id":"/root/children/180/children/8","type":"listItem","loc":{"start":56679,"end":56948,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":0,"e":269}},"dim":["","list.180","listItem.8"],"code":"- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."},{"id":"/root/children/180/children/8/children/0","type":"paragraph","loc":{"start":56681,"end":56948,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":2,"e":269}},"dim":["","list.180","listItem.8","paragraph.0"],"code":"**Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."},{"id":"/root/children/180/children/8/children/0/children/0","type":"strong","loc":{"start":56681,"end":56702,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":2,"e":23}},"dim":["","list.180","listItem.8","paragraph.0","strong.0"],"code":"**Dialog-campaignes**"},{"id":"/root/children/180/children/8/children/0/children/0/children/0","type":"text","loc":{"start":56683,"end":56700,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":4,"e":21}},"dim":["","list.180","listItem.8","paragraph.0","strong.0","text.0"],"code":"Dialog-campaignes"},{"id":"/root/children/180/children/8/children/0/children/1","type":"text","loc":{"start":56702,"end":56704,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":23,"e":25}},"dim":["","list.180","listItem.8","paragraph.0","text.1"],"code":": "},{"id":"/root/children/180/children/8/children/0/children/2","type":"inlineCode","loc":{"start":56704,"end":56737,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":25,"e":58}},"dim":["","list.180","listItem.8","paragraph.0","inlineCode.2"],"code":"`<dialog id=\"dialog-campaignes\">`"},{"id":"/root/children/180/children/8/children/0/children/3","type":"text","loc":{"start":56737,"end":56741,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":58,"e":62}},"dim":["","list.180","listItem.8","paragraph.0","text.3"],"code":" in "},{"id":"/root/children/180/children/8/children/0/children/4","type":"inlineCode","loc":{"start":56741,"end":56759,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":62,"e":80}},"dim":["","list.180","listItem.8","paragraph.0","inlineCode.4"],"code":"`view.jq.html:370`"},{"id":"/root/children/180/children/8/children/0/children/5","type":"text","loc":{"start":56759,"end":56787,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":80,"e":108}},"dim":["","list.180","listItem.8","paragraph.0","text.5"],"code":" populated dynamically from "},{"id":"/root/children/180/children/8/children/0/children/6","type":"inlineCode","loc":{"start":56787,"end":56820,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":108,"e":141}},"dim":["","list.180","listItem.8","paragraph.0","inlineCode.6"],"code":"`Alpine.store(\"glass\").campaigns`"},{"id":"/root/children/180/children/8/children/0/children/7","type":"text","loc":{"start":56820,"end":56852,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":141,"e":173}},"dim":["","list.180","listItem.8","paragraph.0","text.7"],"code":". On close, stores selection to "},{"id":"/root/children/180/children/8/children/0/children/8","type":"inlineCode","loc":{"start":56852,"end":56914,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":173,"e":235}},"dim":["","list.180","listItem.8","paragraph.0","inlineCode.8"],"code":"`window.store(windowStoreKey(\"options/glass/activeCampaign\"))`"},{"id":"/root/children/180/children/8/children/0/children/9","type":"text","loc":{"start":56914,"end":56926,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":235,"e":247}},"dim":["","list.180","listItem.8","paragraph.0","text.9"],"code":". Wiring in "},{"id":"/root/children/180/children/8/children/0/children/10","type":"inlineCode","loc":{"start":56926,"end":56947,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":247,"e":268}},"dim":["","list.180","listItem.8","paragraph.0","inlineCode.10"],"code":"`player.js:1052-1075`"},{"id":"/root/children/180/children/8/children/0/children/11","type":"text","loc":{"start":56947,"end":56948,"line":{"s":947,"e":947,"code":["- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`."]},"column":{"s":268,"e":269}},"dim":["","list.180","listItem.8","paragraph.0","text.11"],"code":"."},{"id":"/root/children/180/children/9","type":"listItem","loc":{"start":56949,"end":57162,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":0,"e":213}},"dim":["","list.180","listItem.9"],"code":"- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."},{"id":"/root/children/180/children/9/children/0","type":"paragraph","loc":{"start":56951,"end":57162,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":2,"e":213}},"dim":["","list.180","listItem.9","paragraph.0"],"code":"**After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."},{"id":"/root/children/180/children/9/children/0/children/0","type":"strong","loc":{"start":56951,"end":56967,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":2,"e":18}},"dim":["","list.180","listItem.9","paragraph.0","strong.0"],"code":"**After voting**"},{"id":"/root/children/180/children/9/children/0/children/0/children/0","type":"text","loc":{"start":56953,"end":56965,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":4,"e":16}},"dim":["","list.180","listItem.9","paragraph.0","strong.0","text.0"],"code":"After voting"},{"id":"/root/children/180/children/9/children/0/children/1","type":"text","loc":{"start":56967,"end":56978,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":18,"e":29}},"dim":["","list.180","listItem.9","paragraph.0","text.1"],"code":", dispatch "},{"id":"/root/children/180/children/9/children/0/children/2","type":"inlineCode","loc":{"start":56978,"end":57029,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":29,"e":80}},"dim":["","list.180","listItem.9","paragraph.0","inlineCode.2"],"code":"`document.dispatchEvent(new Event(\"paper:reload\"))`"},{"id":"/root/children/180/children/9/children/0/children/3","type":"text","loc":{"start":57029,"end":57068,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":80,"e":119}},"dim":["","list.180","listItem.9","paragraph.0","text.3"],"code":" to refresh the dashboard (listener in "},{"id":"/root/children/180/children/9/children/0/children/4","type":"inlineCode","loc":{"start":57068,"end":57083,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":119,"e":134}},"dim":["","list.180","listItem.9","paragraph.0","inlineCode.4"],"code":"`player.js:989`"},{"id":"/root/children/180/children/9/children/0/children/5","type":"text","loc":{"start":57083,"end":57090,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":134,"e":141}},"dim":["","list.180","listItem.9","paragraph.0","text.5"],"code":"). Use "},{"id":"/root/children/180/children/9/children/0/children/6","type":"inlineCode","loc":{"start":57090,"end":57114,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":141,"e":165}},"dim":["","list.180","listItem.9","paragraph.0","inlineCode.6"],"code":"`echoOut({vote: {...}})`"},{"id":"/root/children/180/children/9/children/0/children/7","type":"text","loc":{"start":57114,"end":57162,"line":{"s":948,"e":948,"code":["- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout."]},"column":{"s":165,"e":213}},"dim":["","list.180","listItem.9","paragraph.0","text.7"],"code":" to broadcast to other clients via STOMP fanout."},{"id":"/root/children/181","type":"heading","loc":{"start":57164,"end":57186,"line":{"s":950,"e":950,"code":["## Touchbar system API"]},"column":{"s":0,"e":22}},"dim":["","heading.181"],"code":"## Touchbar system API","symbName":"heading","symbRange":[57188,57860],"symbRangeL":[950,959],"outerCode":"\n- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show\n- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.","outerHtml":"\n<ul><li>`window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show</li><li>`window.touchbarDispatch(action, element)` — button click dispatch</li><li>`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap</li><li>`data-tooltip` — attribute for click/touch triggered toolbar</li><li>Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)</li><li>`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types</li><li><b>resultAction dispatch</b> (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.</li></ul>"},{"id":"/root/children/181/children/0","type":"text","loc":{"start":57167,"end":57186,"line":{"s":950,"e":950,"code":["## Touchbar system API"]},"column":{"s":3,"e":22}},"dim":["","heading.181","text.0"],"code":"Touchbar system API"},{"id":"/root/children/182","type":"list","loc":{"start":57188,"end":57860,"line":{"s":952,"e":958,"code":["- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show","- `window.touchbarDispatch(action, element)` — button click dispatch","- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap","- `data-tooltip` — attribute for click/touch triggered toolbar","- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)","- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types","- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":0,"e":242}},"dim":["","list.182"],"code":"- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show\n- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.","symbName":"list","symbRange":[57862,57884],"symbRangeL":[952,961],"outerCode":"- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.\n\n## Architecture quirks","outerHtml":"<ul><li>`window.touchbarDispatch(action, element)` — button click dispatch</li><li>`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap</li><li>`data-tooltip` — attribute for click/touch triggered toolbar</li><li>Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)</li><li>`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types</li><li><b>resultAction dispatch</b> (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.</li></ul>\n\n<h2>Architecture quirks</h2>"},{"id":"/root/children/182/children/0","type":"listItem","loc":{"start":57188,"end":57261,"line":{"s":952,"e":952,"code":["- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show"]},"column":{"s":0,"e":73}},"dim":["","list.182","listItem.0"],"code":"- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show"},{"id":"/root/children/182/children/0/children/0","type":"paragraph","loc":{"start":57190,"end":57261,"line":{"s":952,"e":952,"code":["- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show"]},"column":{"s":2,"e":73}},"dim":["","list.182","listItem.0","paragraph.0"],"code":"`window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show"},{"id":"/root/children/182/children/0/children/0/children/0","type":"inlineCode","loc":{"start":57190,"end":57241,"line":{"s":952,"e":952,"code":["- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show"]},"column":{"s":2,"e":53}},"dim":["","list.182","listItem.0","paragraph.0","inlineCode.0"],"code":"`window.touchbarShow(\"$definition|params\", {x, y})`"},{"id":"/root/children/182/children/0/children/0/children/1","type":"text","loc":{"start":57241,"end":57261,"line":{"s":952,"e":952,"code":["- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show"]},"column":{"s":53,"e":73}},"dim":["","list.182","listItem.0","paragraph.0","text.1"],"code":" — programmatic show"},{"id":"/root/children/182/children/1","type":"listItem","loc":{"start":57262,"end":57330,"line":{"s":953,"e":953,"code":["- `window.touchbarDispatch(action, element)` — button click dispatch"]},"column":{"s":0,"e":68}},"dim":["","list.182","listItem.1"],"code":"- `window.touchbarDispatch(action, element)` — button click dispatch"},{"id":"/root/children/182/children/1/children/0","type":"paragraph","loc":{"start":57264,"end":57330,"line":{"s":953,"e":953,"code":["- `window.touchbarDispatch(action, element)` — button click dispatch"]},"column":{"s":2,"e":68}},"dim":["","list.182","listItem.1","paragraph.0"],"code":"`window.touchbarDispatch(action, element)` — button click dispatch"},{"id":"/root/children/182/children/1/children/0/children/0","type":"inlineCode","loc":{"start":57264,"end":57306,"line":{"s":953,"e":953,"code":["- `window.touchbarDispatch(action, element)` — button click dispatch"]},"column":{"s":2,"e":44}},"dim":["","list.182","listItem.1","paragraph.0","inlineCode.0"],"code":"`window.touchbarDispatch(action, element)`"},{"id":"/root/children/182/children/1/children/0/children/1","type":"text","loc":{"start":57306,"end":57330,"line":{"s":953,"e":953,"code":["- `window.touchbarDispatch(action, element)` — button click dispatch"]},"column":{"s":44,"e":68}},"dim":["","list.182","listItem.1","paragraph.0","text.1"],"code":" — button click dispatch"},{"id":"/root/children/182/children/2","type":"listItem","loc":{"start":57331,"end":57412,"line":{"s":954,"e":954,"code":["- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"]},"column":{"s":0,"e":81}},"dim":["","list.182","listItem.2"],"code":"- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"},{"id":"/root/children/182/children/2/children/0","type":"paragraph","loc":{"start":57333,"end":57412,"line":{"s":954,"e":954,"code":["- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"]},"column":{"s":2,"e":81}},"dim":["","list.182","listItem.2","paragraph.0"],"code":"`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"},{"id":"/root/children/182/children/2/children/0/children/0","type":"inlineCode","loc":{"start":57333,"end":57362,"line":{"s":954,"e":954,"code":["- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"]},"column":{"s":2,"e":31}},"dim":["","list.182","listItem.2","paragraph.0","inlineCode.0"],"code":"`data-touchpad=\"type|params\"`"},{"id":"/root/children/182/children/2/children/0/children/1","type":"text","loc":{"start":57362,"end":57391,"line":{"s":954,"e":954,"code":["- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"]},"column":{"s":31,"e":60}},"dim":["","list.182","listItem.2","paragraph.0","text.1"],"code":" — attribute on glass search "},{"id":"/root/children/182/children/2/children/0/children/2","type":"inlineCode","loc":{"start":57391,"end":57397,"line":{"s":954,"e":954,"code":["- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"]},"column":{"s":60,"e":66}},"dim":["","list.182","listItem.2","paragraph.0","inlineCode.2"],"code":"`<li>`"},{"id":"/root/children/182/children/2/children/0/children/3","type":"text","loc":{"start":57397,"end":57412,"line":{"s":954,"e":954,"code":["- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap"]},"column":{"s":66,"e":81}},"dim":["","list.182","listItem.2","paragraph.0","text.3"],"code":" for double-tap"},{"id":"/root/children/182/children/3","type":"listItem","loc":{"start":57413,"end":57475,"line":{"s":955,"e":955,"code":["- `data-tooltip` — attribute for click/touch triggered toolbar"]},"column":{"s":0,"e":62}},"dim":["","list.182","listItem.3"],"code":"- `data-tooltip` — attribute for click/touch triggered toolbar"},{"id":"/root/children/182/children/3/children/0","type":"paragraph","loc":{"start":57415,"end":57475,"line":{"s":955,"e":955,"code":["- `data-tooltip` — attribute for click/touch triggered toolbar"]},"column":{"s":2,"e":62}},"dim":["","list.182","listItem.3","paragraph.0"],"code":"`data-tooltip` — attribute for click/touch triggered toolbar"},{"id":"/root/children/182/children/3/children/0/children/0","type":"inlineCode","loc":{"start":57415,"end":57429,"line":{"s":955,"e":955,"code":["- `data-tooltip` — attribute for click/touch triggered toolbar"]},"column":{"s":2,"e":16}},"dim":["","list.182","listItem.3","paragraph.0","inlineCode.0"],"code":"`data-tooltip`"},{"id":"/root/children/182/children/3/children/0/children/1","type":"text","loc":{"start":57429,"end":57475,"line":{"s":955,"e":955,"code":["- `data-tooltip` — attribute for click/touch triggered toolbar"]},"column":{"s":16,"e":62}},"dim":["","list.182","listItem.3","paragraph.0","text.1"],"code":" — attribute for click/touch triggered toolbar"},{"id":"/root/children/182/children/4","type":"listItem","loc":{"start":57476,"end":57531,"line":{"s":956,"e":956,"code":["- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"]},"column":{"s":0,"e":55}},"dim":["","list.182","listItem.4"],"code":"- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"},{"id":"/root/children/182/children/4/children/0","type":"paragraph","loc":{"start":57478,"end":57531,"line":{"s":956,"e":956,"code":["- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"]},"column":{"s":2,"e":55}},"dim":["","list.182","listItem.4","paragraph.0"],"code":"Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"},{"id":"/root/children/182/children/4/children/0/children/0","type":"text","loc":{"start":57478,"end":57493,"line":{"s":956,"e":956,"code":["- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"]},"column":{"s":2,"e":17}},"dim":["","list.182","listItem.4","paragraph.0","text.0"],"code":"Button defs in "},{"id":"/root/children/182/children/4/children/0/children/1","type":"inlineCode","loc":{"start":57493,"end":57507,"line":{"s":956,"e":956,"code":["- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"]},"column":{"s":17,"e":31}},"dim":["","list.182","listItem.4","paragraph.0","inlineCode.1"],"code":"`toolbarsDefs`"},{"id":"/root/children/182/children/4/children/0/children/2","type":"text","loc":{"start":57507,"end":57509,"line":{"s":956,"e":956,"code":["- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"]},"column":{"s":31,"e":33}},"dim":["","list.182","listItem.4","paragraph.0","text.2"],"code":" ("},{"id":"/root/children/182/children/4/children/0/children/3","type":"inlineCode","loc":{"start":57509,"end":57530,"line":{"s":956,"e":956,"code":["- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"]},"column":{"s":33,"e":54}},"dim":["","list.182","listItem.4","paragraph.0","inlineCode.3"],"code":"`ui-touchbar-defs.js`"},{"id":"/root/children/182/children/4/children/0/children/4","type":"text","loc":{"start":57530,"end":57531,"line":{"s":956,"e":956,"code":["- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)"]},"column":{"s":54,"e":55}},"dim":["","list.182","listItem.4","paragraph.0","text.4"],"code":")"},{"id":"/root/children/182/children/5","type":"listItem","loc":{"start":57532,"end":57617,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":0,"e":85}},"dim":["","list.182","listItem.5"],"code":"- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"},{"id":"/root/children/182/children/5/children/0","type":"paragraph","loc":{"start":57534,"end":57617,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":2,"e":85}},"dim":["","list.182","listItem.5","paragraph.0"],"code":"`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"},{"id":"/root/children/182/children/5/children/0/children/0","type":"inlineCode","loc":{"start":57534,"end":57549,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":2,"e":17}},"dim":["","list.182","listItem.5","paragraph.0","inlineCode.0"],"code":"`$resultAction`"},{"id":"/root/children/182/children/5/children/0/children/1","type":"text","loc":{"start":57549,"end":57551,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":17,"e":19}},"dim":["","list.182","listItem.5","paragraph.0","text.1"],"code":", "},{"id":"/root/children/182/children/5/children/0/children/2","type":"inlineCode","loc":{"start":57551,"end":57568,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":19,"e":36}},"dim":["","list.182","listItem.5","paragraph.0","inlineCode.2"],"code":"`$fragmentAction`"},{"id":"/root/children/182/children/5/children/0/children/3","type":"text","loc":{"start":57568,"end":57570,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":36,"e":38}},"dim":["","list.182","listItem.5","paragraph.0","text.3"],"code":", "},{"id":"/root/children/182/children/5/children/0/children/4","type":"inlineCode","loc":{"start":57570,"end":57588,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":38,"e":56}},"dim":["","list.182","listItem.5","paragraph.0","inlineCode.4"],"code":"`$statusTLToolbar`"},{"id":"/root/children/182/children/5/children/0/children/5","type":"text","loc":{"start":57588,"end":57617,"line":{"s":957,"e":957,"code":["- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types"]},"column":{"s":56,"e":85}},"dim":["","list.182","listItem.5","paragraph.0","text.5"],"code":" are the active toolbar types"},{"id":"/root/children/182/children/6","type":"listItem","loc":{"start":57618,"end":57860,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":0,"e":242}},"dim":["","list.182","listItem.6"],"code":"- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."},{"id":"/root/children/182/children/6/children/0","type":"paragraph","loc":{"start":57620,"end":57860,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":2,"e":242}},"dim":["","list.182","listItem.6","paragraph.0"],"code":"**resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."},{"id":"/root/children/182/children/6/children/0/children/0","type":"strong","loc":{"start":57620,"end":57645,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":2,"e":27}},"dim":["","list.182","listItem.6","paragraph.0","strong.0"],"code":"**resultAction dispatch**"},{"id":"/root/children/182/children/6/children/0/children/0/children/0","type":"text","loc":{"start":57622,"end":57643,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":4,"e":25}},"dim":["","list.182","listItem.6","paragraph.0","strong.0","text.0"],"code":"resultAction dispatch"},{"id":"/root/children/182/children/6/children/0/children/1","type":"text","loc":{"start":57645,"end":57647,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":27,"e":29}},"dim":["","list.182","listItem.6","paragraph.0","text.1"],"code":" ("},{"id":"/root/children/182/children/6/children/0/children/2","type":"inlineCode","loc":{"start":57647,"end":57667,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":29,"e":49}},"dim":["","list.182","listItem.6","paragraph.0","inlineCode.2"],"code":"`ui-touchbar.js:822`"},{"id":"/root/children/182/children/6/children/0/children/3","type":"text","loc":{"start":57667,"end":57690,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":49,"e":72}},"dim":["","list.182","listItem.6","paragraph.0","text.3"],"code":"): inline handlers for "},{"id":"/root/children/182/children/6/children/0/children/4","type":"inlineCode","loc":{"start":57690,"end":57699,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":72,"e":81}},"dim":["","list.182","listItem.6","paragraph.0","inlineCode.4"],"code":"`copyRef`"},{"id":"/root/children/182/children/6/children/0/children/5","type":"text","loc":{"start":57699,"end":57704,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":81,"e":86}},"dim":["","list.182","listItem.6","paragraph.0","text.5"],"code":" and "},{"id":"/root/children/182/children/6/children/0/children/6","type":"inlineCode","loc":{"start":57704,"end":57722,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":86,"e":104}},"dim":["","list.182","listItem.6","paragraph.0","inlineCode.6"],"code":"`vote-cast/uncast`"},{"id":"/root/children/182/children/6/children/0/children/7","type":"text","loc":{"start":57722,"end":57743,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":104,"e":125}},"dim":["","list.182","listItem.6","paragraph.0","text.7"],"code":". Vote handler calls "},{"id":"/root/children/182/children/6/children/0/children/8","type":"inlineCode","loc":{"start":57743,"end":57771,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":125,"e":153}},"dim":["","list.182","listItem.6","paragraph.0","inlineCode.8"],"code":"`voteRepo(proxy.STATE, ...)`"},{"id":"/root/children/182/children/6/children/0/children/9","type":"text","loc":{"start":57771,"end":57797,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":153,"e":179}},"dim":["","list.182","listItem.6","paragraph.0","text.9"],"code":", shows notyf, dispatches "},{"id":"/root/children/182/children/6/children/0/children/10","type":"inlineCode","loc":{"start":57797,"end":57813,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":179,"e":195}},"dim":["","list.182","listItem.6","paragraph.0","inlineCode.10"],"code":"`\"paper:reload\"`"},{"id":"/root/children/182/children/6/children/0/children/11","type":"text","loc":{"start":57813,"end":57833,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":195,"e":215}},"dim":["","list.182","listItem.6","paragraph.0","text.11"],"code":", hides toolbar via "},{"id":"/root/children/182/children/6/children/0/children/12","type":"inlineCode","loc":{"start":57833,"end":57859,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":215,"e":241}},"dim":["","list.182","listItem.6","paragraph.0","inlineCode.12"],"code":"`.finally(() => fnHide())`"},{"id":"/root/children/182/children/6/children/0/children/13","type":"text","loc":{"start":57859,"end":57860,"line":{"s":958,"e":958,"code":["- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`."]},"column":{"s":241,"e":242}},"dim":["","list.182","listItem.6","paragraph.0","text.13"],"code":"."},{"id":"/root/children/183","type":"heading","loc":{"start":57862,"end":57884,"line":{"s":960,"e":960,"code":["## Architecture quirks"]},"column":{"s":0,"e":22}},"dim":["","heading.183"],"code":"## Architecture quirks","symbName":"heading","symbRange":[57886,59208],"symbRangeL":[960,969],"outerCode":"\n- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).\n- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).","outerHtml":"\n<ul><li>`window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).</li><li>`proxy.STATE` is the mutable global state object (passed by reference).</li><li>Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.</li><li>Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.</li><li>Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.</li><li>`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.</li><li><b>`echoOut`</b> (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).</li></ul>"},{"id":"/root/children/183/children/0","type":"text","loc":{"start":57865,"end":57884,"line":{"s":960,"e":960,"code":["## Architecture quirks"]},"column":{"s":3,"e":22}},"dim":["","heading.183","text.0"],"code":"Architecture quirks"},{"id":"/root/children/184","type":"list","loc":{"start":57886,"end":59208,"line":{"s":962,"e":968,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).","- `proxy.STATE` is the mutable global state object (passed by reference).","- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.","- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.","- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.","- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.","- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":0,"e":262}},"dim":["","list.184"],"code":"- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).\n- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).","symbName":"list","symbRange":[59210,59269],"symbRangeL":[962,973],"outerCode":"- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).\n\n## Heading-changes / Fragment management\n\n### Files changed","outerHtml":"<ul><li>`proxy.STATE` is the mutable global state object (passed by reference).</li><li>Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.</li><li>Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.</li><li>Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.</li><li>`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.</li><li><b>`echoOut`</b> (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).</li></ul>\n\n<h2>Heading-changes / Fragment management</h2>\n\n<h3>Files changed</h3>"},{"id":"/root/children/184/children/0","type":"listItem","loc":{"start":57886,"end":58123,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":0,"e":237}},"dim":["","list.184","listItem.0"],"code":"- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."},{"id":"/root/children/184/children/0/children/0","type":"paragraph","loc":{"start":57888,"end":58123,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":2,"e":237}},"dim":["","list.184","listItem.0","paragraph.0"],"code":"`window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."},{"id":"/root/children/184/children/0/children/0/children/0","type":"inlineCode","loc":{"start":57888,"end":57906,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":2,"e":20}},"dim":["","list.184","listItem.0","paragraph.0","inlineCode.0"],"code":"`window.ctrlPaper`"},{"id":"/root/children/184/children/0/children/0/children/1","type":"text","loc":{"start":57906,"end":57926,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":20,"e":40}},"dim":["","list.184","listItem.0","paragraph.0","text.1"],"code":" is only set inside "},{"id":"/root/children/184/children/0/children/0/children/2","type":"inlineCode","loc":{"start":57926,"end":57941,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":40,"e":55}},"dim":["","list.184","listItem.0","paragraph.0","inlineCode.2"],"code":"`renderPaper()`"},{"id":"/root/children/184/children/0/children/0/children/3","type":"text","loc":{"start":57941,"end":58013,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":55,"e":127}},"dim":["","list.184","listItem.0","paragraph.0","text.3"],"code":" — NOT available on the glass layer. Result action dispatch handlers in "},{"id":"/root/children/184/children/0/children/0/children/4","type":"inlineCode","loc":{"start":58013,"end":58031,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":127,"e":145}},"dim":["","list.184","listItem.0","paragraph.0","inlineCode.4"],"code":"`touchbarDispatch`"},{"id":"/root/children/184/children/0/children/0/children/5","type":"text","loc":{"start":58031,"end":58060,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":145,"e":174}},"dim":["","list.184","listItem.0","paragraph.0","text.5"],"code":" must be self-contained (use "},{"id":"/root/children/184/children/0/children/0/children/6","type":"inlineCode","loc":{"start":58060,"end":58086,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":174,"e":200}},"dim":["","list.184","listItem.0","paragraph.0","inlineCode.6"],"code":"`proxy.STATE.remoteSqlite`"},{"id":"/root/children/184/children/0/children/0/children/7","type":"text","loc":{"start":58086,"end":58091,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":200,"e":205}},"dim":["","list.184","listItem.0","paragraph.0","text.7"],"code":" and "},{"id":"/root/children/184/children/0/children/0/children/8","type":"inlineCode","loc":{"start":58091,"end":58112,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":205,"e":226}},"dim":["","list.184","listItem.0","paragraph.0","inlineCode.8"],"code":"`proxy.ssss.copyText`"},{"id":"/root/children/184/children/0/children/0/children/9","type":"text","loc":{"start":58112,"end":58123,"line":{"s":962,"e":962,"code":["- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly)."]},"column":{"s":226,"e":237}},"dim":["","list.184","listItem.0","paragraph.0","text.9"],"code":" directly)."},{"id":"/root/children/184/children/1","type":"listItem","loc":{"start":58124,"end":58197,"line":{"s":963,"e":963,"code":["- `proxy.STATE` is the mutable global state object (passed by reference)."]},"column":{"s":0,"e":73}},"dim":["","list.184","listItem.1"],"code":"- `proxy.STATE` is the mutable global state object (passed by reference)."},{"id":"/root/children/184/children/1/children/0","type":"paragraph","loc":{"start":58126,"end":58197,"line":{"s":963,"e":963,"code":["- `proxy.STATE` is the mutable global state object (passed by reference)."]},"column":{"s":2,"e":73}},"dim":["","list.184","listItem.1","paragraph.0"],"code":"`proxy.STATE` is the mutable global state object (passed by reference)."},{"id":"/root/children/184/children/1/children/0/children/0","type":"inlineCode","loc":{"start":58126,"end":58139,"line":{"s":963,"e":963,"code":["- `proxy.STATE` is the mutable global state object (passed by reference)."]},"column":{"s":2,"e":15}},"dim":["","list.184","listItem.1","paragraph.0","inlineCode.0"],"code":"`proxy.STATE`"},{"id":"/root/children/184/children/1/children/0/children/1","type":"text","loc":{"start":58139,"end":58197,"line":{"s":963,"e":963,"code":["- `proxy.STATE` is the mutable global state object (passed by reference)."]},"column":{"s":15,"e":73}},"dim":["","list.184","listItem.1","paragraph.0","text.1"],"code":" is the mutable global state object (passed by reference)."},{"id":"/root/children/184/children/2","type":"listItem","loc":{"start":58198,"end":58281,"line":{"s":964,"e":964,"code":["- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state."]},"column":{"s":0,"e":83}},"dim":["","list.184","listItem.2"],"code":"- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state."},{"id":"/root/children/184/children/2/children/0","type":"paragraph","loc":{"start":58200,"end":58281,"line":{"s":964,"e":964,"code":["- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state."]},"column":{"s":2,"e":83}},"dim":["","list.184","listItem.2","paragraph.0"],"code":"Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state."},{"id":"/root/children/184/children/2/children/0/children/0","type":"text","loc":{"start":58200,"end":58216,"line":{"s":964,"e":964,"code":["- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state."]},"column":{"s":2,"e":18}},"dim":["","list.184","listItem.2","paragraph.0","text.0"],"code":"Alpine store at "},{"id":"/root/children/184/children/2/children/0/children/1","type":"inlineCode","loc":{"start":58216,"end":58239,"line":{"s":964,"e":964,"code":["- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state."]},"column":{"s":18,"e":41}},"dim":["","list.184","listItem.2","paragraph.0","inlineCode.1"],"code":"`Alpine.store(\"glass\")`"},{"id":"/root/children/184/children/2/children/0/children/2","type":"text","loc":{"start":58239,"end":58281,"line":{"s":964,"e":964,"code":["- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state."]},"column":{"s":41,"e":83}},"dim":["","list.184","listItem.2","paragraph.0","text.2"],"code":" — used for touchbar state and menu state."},{"id":"/root/children/184/children/3","type":"listItem","loc":{"start":58282,"end":58544,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":0,"e":262}},"dim":["","list.184","listItem.3"],"code":"- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."},{"id":"/root/children/184/children/3/children/0","type":"paragraph","loc":{"start":58284,"end":58544,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":2,"e":262}},"dim":["","list.184","listItem.3","paragraph.0"],"code":"Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."},{"id":"/root/children/184/children/3/children/0/children/0","type":"text","loc":{"start":58284,"end":58302,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":2,"e":20}},"dim":["","list.184","listItem.3","paragraph.0","text.0"],"code":"Search results in "},{"id":"/root/children/184/children/3/children/0/children/1","type":"inlineCode","loc":{"start":58302,"end":58313,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":20,"e":31}},"dim":["","list.184","listItem.3","paragraph.0","inlineCode.1"],"code":"`onResults`"},{"id":"/root/children/184/children/3/children/0/children/2","type":"text","loc":{"start":58313,"end":58336,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":31,"e":54}},"dim":["","list.184","listItem.3","paragraph.0","text.2"],"code":" callback have fields: "},{"id":"/root/children/184/children/3/children/0/children/3","type":"inlineCode","loc":{"start":58336,"end":58423,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":54,"e":141}},"dim":["","list.184","listItem.3","paragraph.0","inlineCode.3"],"code":"`reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`"},{"id":"/root/children/184/children/3/children/0/children/4","type":"text","loc":{"start":58423,"end":58429,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":141,"e":147}},"dim":["","list.184","listItem.3","paragraph.0","text.4"],"code":". The "},{"id":"/root/children/184/children/3/children/0/children/5","type":"inlineCode","loc":{"start":58429,"end":58433,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":147,"e":151}},"dim":["","list.184","listItem.3","paragraph.0","inlineCode.5"],"code":"`fn`"},{"id":"/root/children/184/children/3/children/0/children/6","type":"text","loc":{"start":58433,"end":58500,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":151,"e":218}},"dim":["","list.184","listItem.3","paragraph.0","text.6"],"code":" field is the raw DB identifier (full path with line number) — use "},{"id":"/root/children/184/children/3/children/0/children/7","type":"inlineCode","loc":{"start":58500,"end":58505,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":218,"e":223}},"dim":["","list.184","listItem.3","paragraph.0","inlineCode.7"],"code":"`uri`"},{"id":"/root/children/184/children/3/children/0/children/8","type":"text","loc":{"start":58505,"end":58544,"line":{"s":965,"e":965,"code":["- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead."]},"column":{"s":223,"e":262}},"dim":["","list.184","listItem.3","paragraph.0","text.8"],"code":" (hash URL) for relative paths instead."},{"id":"/root/children/184/children/4","type":"listItem","loc":{"start":58545,"end":58699,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":0,"e":154}},"dim":["","list.184","listItem.4"],"code":"- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."},{"id":"/root/children/184/children/4/children/0","type":"paragraph","loc":{"start":58547,"end":58699,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":2,"e":154}},"dim":["","list.184","listItem.4","paragraph.0"],"code":"Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."},{"id":"/root/children/184/children/4/children/0/children/0","type":"text","loc":{"start":58547,"end":58575,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":2,"e":30}},"dim":["","list.184","listItem.4","paragraph.0","text.0"],"code":"Voting dashboard reads from "},{"id":"/root/children/184/children/4/children/0/children/1","type":"inlineCode","loc":{"start":58575,"end":58588,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":30,"e":43}},"dim":["","list.184","listItem.4","paragraph.0","inlineCode.1"],"code":"`repo_voting`"},{"id":"/root/children/184/children/4/children/0/children/2","type":"text","loc":{"start":58588,"end":58593,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":43,"e":48}},"dim":["","list.184","listItem.4","paragraph.0","text.2"],"code":" via "},{"id":"/root/children/184/children/4/children/0/children/3","type":"inlineCode","loc":{"start":58593,"end":58642,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":48,"e":97}},"dim":["","list.184","listItem.4","paragraph.0","inlineCode.3"],"code":"`INNER JOIN cache_ast_lake_nodes_ref r USING(id)`"},{"id":"/root/children/184/children/4/children/0/children/4","type":"text","loc":{"start":58642,"end":58652,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":97,"e":107}},"dim":["","list.184","listItem.4","paragraph.0","text.4"],"code":" — if the "},{"id":"/root/children/184/children/4/children/0/children/5","type":"inlineCode","loc":{"start":58652,"end":58657,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":107,"e":112}},"dim":["","list.184","listItem.4","paragraph.0","inlineCode.5"],"code":"`ref`"},{"id":"/root/children/184/children/4/children/0/children/6","type":"text","loc":{"start":58657,"end":58699,"line":{"s":966,"e":966,"code":["- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible."]},"column":{"s":112,"e":154}},"dim":["","list.184","listItem.4","paragraph.0","text.6"],"code":" doesn't match, the vote row is invisible."},{"id":"/root/children/184/children/5","type":"listItem","loc":{"start":58700,"end":58945,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":0,"e":245}},"dim":["","list.184","listItem.5"],"code":"- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."},{"id":"/root/children/184/children/5/children/0","type":"paragraph","loc":{"start":58702,"end":58945,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":2,"e":245}},"dim":["","list.184","listItem.5","paragraph.0"],"code":"`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."},{"id":"/root/children/184/children/5/children/0/children/0","type":"inlineCode","loc":{"start":58702,"end":58716,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":2,"e":16}},"dim":["","list.184","listItem.5","paragraph.0","inlineCode.0"],"code":"`[persistent]`"},{"id":"/root/children/184/children/5/children/0/children/1","type":"text","loc":{"start":58716,"end":58740,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":16,"e":40}},"dim":["","list.184","listItem.5","paragraph.0","text.1"],"code":" attribute on elements ("},{"id":"/root/children/184/children/5/children/0/children/2","type":"inlineCode","loc":{"start":58740,"end":58761,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":40,"e":61}},"dim":["","list.184","listItem.5","paragraph.0","inlineCode.2"],"code":"`player.js:1000-1046`"},{"id":"/root/children/184/children/5/children/0/children/3","type":"text","loc":{"start":58761,"end":58795,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":61,"e":95}},"dim":["","list.184","listItem.5","paragraph.0","text.3"],"code":"): auto-saves/restores values via "},{"id":"/root/children/184/children/5/children/0/children/4","type":"inlineCode","loc":{"start":58795,"end":58853,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":95,"e":153}},"dim":["","list.184","listItem.5","paragraph.0","inlineCode.4"],"code":"`window.store(windowStoreKey(ssss.getNodeTrail(element)))`"},{"id":"/root/children/184/children/5/children/0/children/5","type":"text","loc":{"start":58853,"end":58926,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":153,"e":226}},"dim":["","list.184","listItem.5","paragraph.0","text.5"],"code":". Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on "},{"id":"/root/children/184/children/5/children/0/children/6","type":"inlineCode","loc":{"start":58926,"end":58944,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":226,"e":244}},"dim":["","list.184","listItem.5","paragraph.0","inlineCode.6"],"code":"`sEventGlassReady`"},{"id":"/root/children/184/children/5/children/0/children/7","type":"text","loc":{"start":58944,"end":58945,"line":{"s":967,"e":967,"code":["- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`."]},"column":{"s":244,"e":245}},"dim":["","list.184","listItem.5","paragraph.0","text.7"],"code":"."},{"id":"/root/children/184/children/6","type":"listItem","loc":{"start":58946,"end":59208,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":0,"e":262}},"dim":["","list.184","listItem.6"],"code":"- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."},{"id":"/root/children/184/children/6/children/0","type":"paragraph","loc":{"start":58948,"end":59208,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":2,"e":262}},"dim":["","list.184","listItem.6","paragraph.0"],"code":"**`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."},{"id":"/root/children/184/children/6/children/0/children/0","type":"strong","loc":{"start":58948,"end":58961,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":2,"e":15}},"dim":["","list.184","listItem.6","paragraph.0","strong.0"],"code":"**`echoOut`**"},{"id":"/root/children/184/children/6/children/0/children/0/children/0","type":"inlineCode","loc":{"start":58950,"end":58959,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":4,"e":13}},"dim":["","list.184","listItem.6","paragraph.0","strong.0","inlineCode.0"],"code":"`echoOut`"},{"id":"/root/children/184/children/6/children/0/children/1","type":"text","loc":{"start":58961,"end":58963,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":15,"e":17}},"dim":["","list.184","listItem.6","paragraph.0","text.1"],"code":" ("},{"id":"/root/children/184/children/6/children/0/children/2","type":"inlineCode","loc":{"start":58963,"end":58984,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":17,"e":38}},"dim":["","list.184","listItem.6","paragraph.0","inlineCode.2"],"code":"`player-paper.js:366`"},{"id":"/root/children/184/children/6/children/0/children/3","type":"text","loc":{"start":58984,"end":59013,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":38,"e":67}},"dim":["","list.184","listItem.6","paragraph.0","text.3"],"code":"): emits fanout messages via "},{"id":"/root/children/184/children/6/children/0/children/4","type":"inlineCode","loc":{"start":59013,"end":59065,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":67,"e":119}},"dim":["","list.184","listItem.6","paragraph.0","inlineCode.4"],"code":"`PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`"},{"id":"/root/children/184/children/6/children/0/children/5","type":"text","loc":{"start":59065,"end":59147,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":119,"e":201}},"dim":["","list.184","listItem.6","paragraph.0","text.5"],"code":". Used to broadcast vote events to other connected clients. Available only inside "},{"id":"/root/children/184/children/6/children/0/children/6","type":"inlineCode","loc":{"start":59147,"end":59162,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":201,"e":216}},"dim":["","list.184","listItem.6","paragraph.0","inlineCode.6"],"code":"`renderPaper()`"},{"id":"/root/children/184/children/6/children/0/children/7","type":"text","loc":{"start":59162,"end":59167,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":216,"e":221}},"dim":["","list.184","listItem.6","paragraph.0","text.7"],"code":" and "},{"id":"/root/children/184/children/6/children/0/children/8","type":"inlineCode","loc":{"start":59167,"end":59184,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":221,"e":238}},"dim":["","list.184","listItem.6","paragraph.0","inlineCode.8"],"code":"`dashboardBody()`"},{"id":"/root/children/184/children/6/children/0/children/9","type":"text","loc":{"start":59184,"end":59191,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":238,"e":245}},"dim":["","list.184","listItem.6","paragraph.0","text.9"],"code":" (from "},{"id":"/root/children/184/children/6/children/0/children/10","type":"inlineCode","loc":{"start":59191,"end":59206,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":245,"e":260}},"dim":["","list.184","listItem.6","paragraph.0","inlineCode.10"],"code":"`proxy.echoOut`"},{"id":"/root/children/184/children/6/children/0/children/11","type":"text","loc":{"start":59206,"end":59208,"line":{"s":968,"e":968,"code":["- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`)."]},"column":{"s":260,"e":262}},"dim":["","list.184","listItem.6","paragraph.0","text.11"],"code":")."},{"id":"/root/children/185","type":"heading","loc":{"start":59210,"end":59250,"line":{"s":970,"e":970,"code":["## Heading-changes / Fragment management"]},"column":{"s":0,"e":40}},"dim":["","heading.185"],"code":"## Heading-changes / Fragment management","symbName":"heading","symbRange":[59252,70981],"symbRangeL":[970,971],"outerCode":"","outerHtml":""},{"id":"/root/children/185/children/0","type":"text","loc":{"start":59213,"end":59250,"line":{"s":970,"e":970,"code":["## Heading-changes / Fragment management"]},"column":{"s":3,"e":40}},"dim":["","heading.185","text.0"],"code":"Heading-changes / Fragment management"},{"id":"/root/children/186","type":"heading","loc":{"start":59252,"end":59269,"line":{"s":972,"e":972,"code":["### Files changed"]},"column":{"s":0,"e":17}},"dim":["","heading.186"],"code":"### Files changed","symbName":"heading","symbRange":[59271,59723],"symbRangeL":[972,977],"outerCode":"\n- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`\n- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched","outerHtml":"\n<ul><li>`player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`</li><li>`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter</li><li>`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched</li></ul>"},{"id":"/root/children/186/children/0","type":"text","loc":{"start":59256,"end":59269,"line":{"s":972,"e":972,"code":["### Files changed"]},"column":{"s":4,"e":17}},"dim":["","heading.186","text.0"],"code":"Files changed"},{"id":"/root/children/187","type":"list","loc":{"start":59271,"end":59723,"line":{"s":974,"e":976,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`","- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter","- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":0,"e":178}},"dim":["","list.187"],"code":"- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`\n- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched","symbName":"list","symbRange":[59725,60474],"symbRangeL":[974,995],"outerCode":"- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched\n\n### Dialog rename\n\n`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.\n\n### hasMetaData filter (heading report)\n\n`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.\n\n`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.\n\nDelete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.\n\n### fragmentUrl system\n\nFormat: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).\n\n**Functions:**","outerHtml":"<ul><li>`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter</li><li>`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched</li></ul>\n\n<h3>Dialog rename</h3>\n\n<p>`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.</p>\n\n<h3>hasMetaData filter (heading report)</h3>\n\n<p>`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.</p>\n\n<p>`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.</p>\n\n<p>Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.</p>\n\n<h3>fragmentUrl system</h3>\n\n<p>Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).</p>\n\n<p><b>Functions:</b></p>"},{"id":"/root/children/187/children/0","type":"listItem","loc":{"start":59271,"end":59406,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":0,"e":135}},"dim":["","list.187","listItem.0"],"code":"- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"},{"id":"/root/children/187/children/0/children/0","type":"paragraph","loc":{"start":59273,"end":59406,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":2,"e":135}},"dim":["","list.187","listItem.0","paragraph.0"],"code":"`player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"},{"id":"/root/children/187/children/0/children/0/children/0","type":"inlineCode","loc":{"start":59273,"end":59297,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":2,"e":26}},"dim":["","list.187","listItem.0","paragraph.0","inlineCode.0"],"code":"`player/player-utils.js`"},{"id":"/root/children/187/children/0/children/0/children/1","type":"text","loc":{"start":59297,"end":59315,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":26,"e":44}},"dim":["","list.187","listItem.0","paragraph.0","text.1"],"code":" — new functions: "},{"id":"/root/children/187/children/0/children/0/children/2","type":"inlineCode","loc":{"start":59315,"end":59336,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":44,"e":65}},"dim":["","list.187","listItem.0","paragraph.0","inlineCode.2"],"code":"`validateFragmentUrl`"},{"id":"/root/children/187/children/0/children/0/children/3","type":"text","loc":{"start":59336,"end":59338,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":65,"e":67}},"dim":["","list.187","listItem.0","paragraph.0","text.3"],"code":", "},{"id":"/root/children/187/children/0/children/0/children/4","type":"inlineCode","loc":{"start":59338,"end":59352,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":67,"e":81}},"dim":["","list.187","listItem.0","paragraph.0","inlineCode.4"],"code":"`loadFragment`"},{"id":"/root/children/187/children/0/children/0/children/5","type":"text","loc":{"start":59352,"end":59354,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":81,"e":83}},"dim":["","list.187","listItem.0","paragraph.0","text.5"],"code":", "},{"id":"/root/children/187/children/0/children/0/children/6","type":"inlineCode","loc":{"start":59354,"end":59367,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":83,"e":96}},"dim":["","list.187","listItem.0","paragraph.0","inlineCode.6"],"code":"`hasMetaData`"},{"id":"/root/children/187/children/0/children/0/children/7","type":"text","loc":{"start":59367,"end":59369,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":96,"e":98}},"dim":["","list.187","listItem.0","paragraph.0","text.7"],"code":", "},{"id":"/root/children/187/children/0/children/0/children/8","type":"inlineCode","loc":{"start":59369,"end":59387,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":98,"e":116}},"dim":["","list.187","listItem.0","paragraph.0","inlineCode.8"],"code":"`buildFragmentRef`"},{"id":"/root/children/187/children/0/children/0/children/9","type":"text","loc":{"start":59387,"end":59389,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":116,"e":118}},"dim":["","list.187","listItem.0","paragraph.0","text.9"],"code":", "},{"id":"/root/children/187/children/0/children/0/children/10","type":"inlineCode","loc":{"start":59389,"end":59406,"line":{"s":974,"e":974,"code":["- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`"]},"column":{"s":118,"e":135}},"dim":["","list.187","listItem.0","paragraph.0","inlineCode.10"],"code":"`loadJsonOptions`"},{"id":"/root/children/187/children/1","type":"listItem","loc":{"start":59407,"end":59544,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":0,"e":137}},"dim":["","list.187","listItem.1"],"code":"- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"},{"id":"/root/children/187/children/1/children/0","type":"paragraph","loc":{"start":59409,"end":59544,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":2,"e":137}},"dim":["","list.187","listItem.1","paragraph.0"],"code":"`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"},{"id":"/root/children/187/children/1/children/0/children/0","type":"inlineCode","loc":{"start":59409,"end":59433,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":2,"e":26}},"dim":["","list.187","listItem.1","paragraph.0","inlineCode.0"],"code":"`player/player-paper.js`"},{"id":"/root/children/187/children/1/children/0/children/1","type":"text","loc":{"start":59433,"end":59456,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":26,"e":49}},"dim":["","list.187","listItem.1","paragraph.0","text.1"],"code":" — new touchbar action "},{"id":"/root/children/187/children/1/children/0/children/2","type":"inlineCode","loc":{"start":59456,"end":59468,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":49,"e":61}},"dim":["","list.187","listItem.1","paragraph.0","inlineCode.2"],"code":"`replantRef`"},{"id":"/root/children/187/children/1/children/0/children/3","type":"text","loc":{"start":59468,"end":59472,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":61,"e":65}},"dim":["","list.187","listItem.1","paragraph.0","text.3"],"code":" in "},{"id":"/root/children/187/children/1/children/0/children/4","type":"inlineCode","loc":{"start":59472,"end":59490,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":65,"e":83}},"dim":["","list.187","listItem.1","paragraph.0","inlineCode.4"],"code":"`window.ctrlPaper`"},{"id":"/root/children/187/children/1/children/0/children/5","type":"text","loc":{"start":59490,"end":59492,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":83,"e":85}},"dim":["","list.187","listItem.1","paragraph.0","text.5"],"code":"; "},{"id":"/root/children/187/children/1/children/0/children/6","type":"inlineCode","loc":{"start":59492,"end":59512,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":85,"e":105}},"dim":["","list.187","listItem.1","paragraph.0","inlineCode.6"],"code":"`paperHeadingReport`"},{"id":"/root/children/187/children/1/children/0/children/7","type":"text","loc":{"start":59512,"end":59524,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":105,"e":117}},"dim":["","list.187","listItem.1","paragraph.0","text.7"],"code":" wired with "},{"id":"/root/children/187/children/1/children/0/children/8","type":"inlineCode","loc":{"start":59524,"end":59537,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":117,"e":130}},"dim":["","list.187","listItem.1","paragraph.0","inlineCode.8"],"code":"`hasMetaData`"},{"id":"/root/children/187/children/1/children/0/children/9","type":"text","loc":{"start":59537,"end":59544,"line":{"s":975,"e":975,"code":["- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter"]},"column":{"s":130,"e":137}},"dim":["","list.187","listItem.1","paragraph.0","text.9"],"code":" filter"},{"id":"/root/children/187/children/2","type":"listItem","loc":{"start":59545,"end":59723,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":0,"e":178}},"dim":["","list.187","listItem.2"],"code":"- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"},{"id":"/root/children/187/children/2/children/0","type":"paragraph","loc":{"start":59547,"end":59723,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":2,"e":178}},"dim":["","list.187","listItem.2","paragraph.0"],"code":"`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"},{"id":"/root/children/187/children/2/children/0/children/0","type":"inlineCode","loc":{"start":59547,"end":59568,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":2,"e":23}},"dim":["","list.187","listItem.2","paragraph.0","inlineCode.0"],"code":"`proc-paper-edit.mjs`"},{"id":"/root/children/187/children/2/children/0/children/1","type":"text","loc":{"start":59568,"end":59624,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":23,"e":79}},"dim":["","list.187","listItem.2","paragraph.0","text.1"],"code":" (server-side webhook) — vote deletion logic confirmed: "},{"id":"/root/children/187/children/2/children/0/children/2","type":"inlineCode","loc":{"start":59624,"end":59641,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":79,"e":96}},"dim":["","list.187","listItem.2","paragraph.0","inlineCode.2"],"code":"`resolvedRenames`"},{"id":"/root/children/187/children/2/children/0/children/3","type":"text","loc":{"start":59641,"end":59658,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":96,"e":113}},"dim":["","list.187","listItem.2","paragraph.0","text.3"],"code":" → UPDATE votes, "},{"id":"/root/children/187/children/2/children/0/children/4","type":"inlineCode","loc":{"start":59658,"end":59678,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":113,"e":133}},"dim":["","list.187","listItem.2","paragraph.0","inlineCode.4"],"code":"`confirmedDeletions`"},{"id":"/root/children/187/children/2/children/0/children/5","type":"text","loc":{"start":59678,"end":59723,"line":{"s":976,"e":976,"code":["- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched"]},"column":{"s":133,"e":178}},"dim":["","list.187","listItem.2","paragraph.0","text.5"],"code":" → DELETE votes, skipped items left untouched"},{"id":"/root/children/188","type":"heading","loc":{"start":59725,"end":59742,"line":{"s":978,"e":978,"code":["### Dialog rename"]},"column":{"s":0,"e":17}},"dim":["","heading.188"],"code":"### Dialog rename","symbName":"heading","symbRange":[59744,59846],"symbRangeL":[978,981],"outerCode":"\n`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.","outerHtml":"\n<p>`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.</p>"},{"id":"/root/children/188/children/0","type":"text","loc":{"start":59729,"end":59742,"line":{"s":978,"e":978,"code":["### Dialog rename"]},"column":{"s":4,"e":17}},"dim":["","heading.188","text.0"],"code":"Dialog rename"},{"id":"/root/children/189","type":"paragraph","loc":{"start":59744,"end":59846,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":0,"e":102}},"dim":["","paragraph.189"],"code":"`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."},{"id":"/root/children/189/children/0","type":"inlineCode","loc":{"start":59744,"end":59767,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":0,"e":23}},"dim":["","paragraph.189","inlineCode.0"],"code":"`broken-refs-dialog.js`"},{"id":"/root/children/189/children/1","type":"text","loc":{"start":59767,"end":59770,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":23,"e":26}},"dim":["","paragraph.189","text.1"],"code":" → "},{"id":"/root/children/189/children/2","type":"inlineCode","loc":{"start":59770,"end":59796,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":26,"e":52}},"dim":["","paragraph.189","inlineCode.2"],"code":"`broken-refs-managment.js`"},{"id":"/root/children/189/children/3","type":"text","loc":{"start":59796,"end":59816,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":52,"e":72}},"dim":["","paragraph.189","text.3"],"code":". Import updated in "},{"id":"/root/children/189/children/4","type":"inlineCode","loc":{"start":59816,"end":59827,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":72,"e":83}},"dim":["","paragraph.189","inlineCode.4"],"code":"`player.js`"},{"id":"/root/children/189/children/5","type":"text","loc":{"start":59827,"end":59832,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":83,"e":88}},"dim":["","paragraph.189","text.5"],"code":" and "},{"id":"/root/children/189/children/6","type":"inlineCode","loc":{"start":59832,"end":59845,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":88,"e":101}},"dim":["","paragraph.189","inlineCode.6"],"code":"`portlets.md`"},{"id":"/root/children/189/children/7","type":"text","loc":{"start":59845,"end":59846,"line":{"s":980,"e":980,"code":["`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`."]},"column":{"s":101,"e":102}},"dim":["","paragraph.189","text.7"],"code":"."},{"id":"/root/children/190","type":"heading","loc":{"start":59848,"end":59887,"line":{"s":982,"e":982,"code":["### hasMetaData filter (heading report)"]},"column":{"s":0,"e":39}},"dim":["","heading.190"],"code":"### hasMetaData filter (heading report)","symbName":"heading","symbRange":[59889,60318],"symbRangeL":[982,989],"outerCode":"\n`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.\n\n`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.\n\nDelete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.","outerHtml":"\n<p>`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.</p>\n\n<p>`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.</p>\n\n<p>Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.</p>"},{"id":"/root/children/190/children/0","type":"text","loc":{"start":59852,"end":59887,"line":{"s":982,"e":982,"code":["### hasMetaData filter (heading report)"]},"column":{"s":4,"e":39}},"dim":["","heading.190","text.0"],"code":"hasMetaData filter (heading report)"},{"id":"/root/children/191","type":"paragraph","loc":{"start":59889,"end":60130,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":0,"e":241}},"dim":["","paragraph.191"],"code":"`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."},{"id":"/root/children/191/children/0","type":"inlineCode","loc":{"start":59889,"end":59925,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":0,"e":36}},"dim":["","paragraph.191","inlineCode.0"],"code":"`hasMetaData(PROXY, repoName, refs)`"},{"id":"/root/children/191/children/1","type":"text","loc":{"start":59925,"end":59929,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":36,"e":40}},"dim":["","paragraph.191","text.1"],"code":" in "},{"id":"/root/children/191/children/2","type":"inlineCode","loc":{"start":59929,"end":59946,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":40,"e":57}},"dim":["","paragraph.191","inlineCode.2"],"code":"`player-utils.js`"},{"id":"/root/children/191/children/3","type":"text","loc":{"start":59946,"end":59957,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":57,"e":68}},"dim":["","paragraph.191","text.3"],"code":" — queries "},{"id":"/root/children/191/children/4","type":"inlineCode","loc":{"start":59957,"end":59970,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":68,"e":81}},"dim":["","paragraph.191","inlineCode.4"],"code":"`repo_voting`"},{"id":"/root/children/191/children/5","type":"text","loc":{"start":59970,"end":60011,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":81,"e":122}},"dim":["","paragraph.191","text.5"],"code":" for refs with existing votes, returns a "},{"id":"/root/children/191/children/6","type":"inlineCode","loc":{"start":60011,"end":60016,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":122,"e":127}},"dim":["","paragraph.191","inlineCode.6"],"code":"`Set`"},{"id":"/root/children/191/children/7","type":"text","loc":{"start":60016,"end":60029,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":127,"e":140}},"dim":["","paragraph.191","text.7"],"code":". Wired into "},{"id":"/root/children/191/children/8","type":"inlineCode","loc":{"start":60029,"end":60049,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":140,"e":160}},"dim":["","paragraph.191","inlineCode.8"],"code":"`paperHeadingReport`"},{"id":"/root/children/191/children/9","type":"text","loc":{"start":60049,"end":60060,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":160,"e":171}},"dim":["","paragraph.191","text.9"],"code":" to filter "},{"id":"/root/children/191/children/10","type":"inlineCode","loc":{"start":60060,"end":60071,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":171,"e":182}},"dim":["","paragraph.191","inlineCode.10"],"code":"`allEvents`"},{"id":"/root/children/191/children/11","type":"text","loc":{"start":60071,"end":60130,"line":{"s":984,"e":984,"code":["`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout."]},"column":{"s":182,"e":241}},"dim":["","paragraph.191","text.11"],"code":" so only candidates with votes appear in the report/fanout."},{"id":"/root/children/192","type":"paragraph","loc":{"start":60132,"end":60242,"line":{"s":986,"e":986,"code":["`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."]},"column":{"s":0,"e":110}},"dim":["","paragraph.192"],"code":"`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."},{"id":"/root/children/192/children/0","type":"inlineCode","loc":{"start":60132,"end":60145,"line":{"s":986,"e":986,"code":["`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."]},"column":{"s":0,"e":13}},"dim":["","paragraph.192","inlineCode.0"],"code":"`[preselect]`"},{"id":"/root/children/192/children/1","type":"text","loc":{"start":60145,"end":60168,"line":{"s":986,"e":986,"code":["`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."]},"column":{"s":13,"e":36}},"dim":["","paragraph.192","text.1"],"code":" focus on dialog open: "},{"id":"/root/children/192/children/2","type":"inlineCode","loc":{"start":60168,"end":60221,"line":{"s":986,"e":986,"code":["`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."]},"column":{"s":36,"e":89}},"dim":["","paragraph.192","inlineCode.2"],"code":"`headingDialog.querySelector(\"[preselect]\")?.focus()`"},{"id":"/root/children/192/children/3","type":"text","loc":{"start":60221,"end":60228,"line":{"s":986,"e":986,"code":["`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."]},"column":{"s":89,"e":96}},"dim":["","paragraph.192","text.3"],"code":" after "},{"id":"/root/children/192/children/4","type":"inlineCode","loc":{"start":60228,"end":60241,"line":{"s":986,"e":986,"code":["`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."]},"column":{"s":96,"e":109}},"dim":["","paragraph.192","inlineCode.4"],"code":"`showModal()`"},{"id":"/root/children/192/children/5","type":"text","loc":{"start":60241,"end":60242,"line":{"s":986,"e":986,"code":["`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`."]},"column":{"s":109,"e":110}},"dim":["","paragraph.192","text.5"],"code":"."},{"id":"/root/children/193","type":"paragraph","loc":{"start":60244,"end":60318,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":0,"e":74}},"dim":["","paragraph.193"],"code":"Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."},{"id":"/root/children/193/children/0","type":"text","loc":{"start":60244,"end":60264,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":0,"e":20}},"dim":["","paragraph.193","text.0"],"code":"Delete-option icon: "},{"id":"/root/children/193/children/1","type":"inlineCode","loc":{"start":60264,"end":60274,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":20,"e":30}},"dim":["","paragraph.193","inlineCode.1"],"code":"`si-cross`"},{"id":"/root/children/193/children/2","type":"text","loc":{"start":60274,"end":60277,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":30,"e":33}},"dim":["","paragraph.193","text.2"],"code":" → "},{"id":"/root/children/193/children/3","type":"inlineCode","loc":{"start":60277,"end":60287,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":33,"e":43}},"dim":["","paragraph.193","inlineCode.3"],"code":"`si-trash`"},{"id":"/root/children/193/children/4","type":"text","loc":{"start":60287,"end":60291,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":43,"e":47}},"dim":["","paragraph.193","text.4"],"code":" in "},{"id":"/root/children/193/children/5","type":"inlineCode","loc":{"start":60291,"end":60317,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":47,"e":73}},"dim":["","paragraph.193","inlineCode.5"],"code":"`broken-refs-managment.js`"},{"id":"/root/children/193/children/6","type":"text","loc":{"start":60317,"end":60318,"line":{"s":988,"e":988,"code":["Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`."]},"column":{"s":73,"e":74}},"dim":["","paragraph.193","text.6"],"code":"."},{"id":"/root/children/194","type":"heading","loc":{"start":60320,"end":60342,"line":{"s":990,"e":990,"code":["### fragmentUrl system"]},"column":{"s":0,"e":22}},"dim":["","heading.194"],"code":"### fragmentUrl system","symbName":"heading","symbRange":[60344,61911],"symbRangeL":[990,1010],"outerCode":"\nFormat: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).\n\n**Functions:**\n\n- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)\n- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.","outerHtml":"\n<p>Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).</p>\n\n<p><b>Functions:</b></p>\n\n<ul><li>`validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)</li><li>`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:<ol><li>Validates fragment URL format</li><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li><li>Returns `{ text, pathInRepo, startLine, endLine }` or `null`</li></ol></li></ul>\n\n<ul><li>`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.</li></ul>"},{"id":"/root/children/194/children/0","type":"text","loc":{"start":60324,"end":60342,"line":{"s":990,"e":990,"code":["### fragmentUrl system"]},"column":{"s":4,"e":22}},"dim":["","heading.194","text.0"],"code":"fragmentUrl system"},{"id":"/root/children/195","type":"paragraph","loc":{"start":60344,"end":60458,"line":{"s":992,"e":992,"code":["Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`)."]},"column":{"s":0,"e":114}},"dim":["","paragraph.195"],"code":"Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`)."},{"id":"/root/children/195/children/0","type":"text","loc":{"start":60344,"end":60352,"line":{"s":992,"e":992,"code":["Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`)."]},"column":{"s":0,"e":8}},"dim":["","paragraph.195","text.0"],"code":"Format: "},{"id":"/root/children/195/children/1","type":"inlineCode","loc":{"start":60352,"end":60411,"line":{"s":992,"e":992,"code":["Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`)."]},"column":{"s":8,"e":67}},"dim":["","paragraph.195","inlineCode.1"],"code":"`<relative-file-path.ext>::<sanitized-breadcrumb/segments>`"},{"id":"/root/children/195/children/2","type":"text","loc":{"start":60411,"end":60418,"line":{"s":992,"e":992,"code":["Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`)."]},"column":{"s":67,"e":74}},"dim":["","paragraph.195","text.2"],"code":" (e.g. "},{"id":"/root/children/195/children/3","type":"inlineCode","loc":{"start":60418,"end":60456,"line":{"s":992,"e":992,"code":["Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`)."]},"column":{"s":74,"e":112}},"dim":["","paragraph.195","inlineCode.3"],"code":"`paper/scratchpad.mdd::candidateDruhy`"},{"id":"/root/children/195/children/4","type":"text","loc":{"start":60456,"end":60458,"line":{"s":992,"e":992,"code":["Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`)."]},"column":{"s":112,"e":114}},"dim":["","paragraph.195","text.4"],"code":")."},{"id":"/root/children/196","type":"paragraph","loc":{"start":60460,"end":60474,"line":{"s":994,"e":994,"code":["**Functions:**"]},"column":{"s":0,"e":14}},"dim":["","paragraph.196"],"code":"**Functions:**"},{"id":"/root/children/196/children/0","type":"strong","loc":{"start":60460,"end":60474,"line":{"s":994,"e":994,"code":["**Functions:**"]},"column":{"s":0,"e":14}},"dim":["","paragraph.196","strong.0"],"code":"**Functions:**"},{"id":"/root/children/196/children/0/children/0","type":"text","loc":{"start":60462,"end":60472,"line":{"s":994,"e":994,"code":["**Functions:**"]},"column":{"s":2,"e":12}},"dim":["","paragraph.196","strong.0","text.0"],"code":"Functions:"},{"id":"/root/children/197","type":"list","loc":{"start":60476,"end":61911,"line":{"s":996,"e":1009,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)","- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:","  1. Validates fragment URL format","  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)","  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)","  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix","  5. Determines range end:","     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content","     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary","  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`","  7. Slices `lines[start..end]` (start = lineNum - 1)","  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`","","- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."]},"column":{"s":0,"e":213}},"dim":["","list.197"],"code":"- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)\n- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.","symbName":"list","symbRange":[61913,62015],"symbRangeL":[996,1014],"outerCode":"- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.\n\n### replantRef touchbar action\n\nTriggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:","outerHtml":"<ul><li>`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:<ol><li>Validates fragment URL format</li><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li><li>Returns `{ text, pathInRepo, startLine, endLine }` or `null`</li></ol></li></ul>\n\n<ul><li>`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.</li></ul>\n\n<h3>replantRef touchbar action</h3>\n\n<p>Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:</p>"},{"id":"/root/children/197/children/0","type":"listItem","loc":{"start":60476,"end":60591,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":0,"e":115}},"dim":["","list.197","listItem.0"],"code":"- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"},{"id":"/root/children/197/children/0/children/0","type":"paragraph","loc":{"start":60478,"end":60591,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":2,"e":115}},"dim":["","list.197","listItem.0","paragraph.0"],"code":"`validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"},{"id":"/root/children/197/children/0/children/0/children/0","type":"inlineCode","loc":{"start":60478,"end":60502,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":2,"e":26}},"dim":["","list.197","listItem.0","paragraph.0","inlineCode.0"],"code":"`validateFragmentUrl(s)`"},{"id":"/root/children/197/children/0/children/0/children/1","type":"text","loc":{"start":60502,"end":60515,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":26,"e":39}},"dim":["","list.197","listItem.0","paragraph.0","text.1"],"code":" — validates "},{"id":"/root/children/197/children/0/children/0/children/2","type":"inlineCode","loc":{"start":60515,"end":60536,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":39,"e":60}},"dim":["","list.197","listItem.0","paragraph.0","inlineCode.2"],"code":"`path.ext::seg1/seg2`"},{"id":"/root/children/197/children/0/children/0/children/3","type":"text","loc":{"start":60536,"end":60550,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":60,"e":74}},"dim":["","list.197","listItem.0","paragraph.0","text.3"],"code":" (exactly one "},{"id":"/root/children/197/children/0/children/0/children/4","type":"inlineCode","loc":{"start":60550,"end":60554,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":74,"e":78}},"dim":["","list.197","listItem.0","paragraph.0","inlineCode.4"],"code":"`::`"},{"id":"/root/children/197/children/0/children/0/children/5","type":"text","loc":{"start":60554,"end":60591,"line":{"s":996,"e":996,"code":["- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)"]},"column":{"s":78,"e":115}},"dim":["","list.197","listItem.0","paragraph.0","text.5"],"code":", file extension, non-empty segments)"},{"id":"/root/children/197/children/1","type":"listItem","loc":{"start":60592,"end":61696,"line":{"s":997,"e":1007,"code":["- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:","  1. Validates fragment URL format","  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)","  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)","  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix","  5. Determines range end:","     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content","     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary","  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`","  7. Slices `lines[start..end]` (start = lineNum - 1)","  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":0,"e":65}},"dim":["","list.197","listItem.1"],"code":"- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"},{"id":"/root/children/197/children/1/children/0","type":"paragraph","loc":{"start":60594,"end":60654,"line":{"s":997,"e":997,"code":["- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:"]},"column":{"s":2,"e":62}},"dim":["","list.197","listItem.1","paragraph.0"],"code":"`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:"},{"id":"/root/children/197/children/1/children/0/children/0","type":"inlineCode","loc":{"start":60594,"end":60653,"line":{"s":997,"e":997,"code":["- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:"]},"column":{"s":2,"e":61}},"dim":["","list.197","listItem.1","paragraph.0","inlineCode.0"],"code":"`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`"},{"id":"/root/children/197/children/1/children/0/children/1","type":"text","loc":{"start":60653,"end":60654,"line":{"s":997,"e":997,"code":["- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:"]},"column":{"s":61,"e":62}},"dim":["","list.197","listItem.1","paragraph.0","text.1"],"code":":"},{"id":"/root/children/197/children/1/children/1","type":"list","loc":{"start":60657,"end":61696,"line":{"s":998,"e":1007,"code":["  1. Validates fragment URL format","  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)","  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)","  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix","  5. Determines range end:","     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content","     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary","  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`","  7. Slices `lines[start..end]` (start = lineNum - 1)","  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":2,"e":65}},"dim":["","list.197","listItem.1","list.1"],"code":"1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`","symbName":"list","symbRange":[null,null],"symbRangeL":[998,1007],"outerCode":"  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)","outerHtml":"<ol><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li></ol>"},{"id":"/root/children/197/children/1/children/1/children/0","type":"listItem","loc":{"start":60657,"end":60689,"line":{"s":998,"e":998,"code":["  1. Validates fragment URL format"]},"column":{"s":2,"e":34}},"dim":["","list.197","listItem.1","list.1","listItem.0"],"code":"1. Validates fragment URL format"},{"id":"/root/children/197/children/1/children/1/children/0/children/0","type":"paragraph","loc":{"start":60660,"end":60689,"line":{"s":998,"e":998,"code":["  1. Validates fragment URL format"]},"column":{"s":5,"e":34}},"dim":["","list.197","listItem.1","list.1","listItem.0","paragraph.0"],"code":"Validates fragment URL format"},{"id":"/root/children/197/children/1/children/1/children/0/children/0/children/0","type":"text","loc":{"start":60660,"end":60689,"line":{"s":998,"e":998,"code":["  1. Validates fragment URL format"]},"column":{"s":5,"e":34}},"dim":["","list.197","listItem.1","list.1","listItem.0","paragraph.0","text.0"],"code":"Validates fragment URL format"},{"id":"/root/children/197/children/1/children/1/children/1","type":"listItem","loc":{"start":60692,"end":60870,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":2,"e":180}},"dim":["","list.197","listItem.1","list.1","listItem.1"],"code":"2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"},{"id":"/root/children/197/children/1/children/1/children/1/children/0","type":"paragraph","loc":{"start":60695,"end":60870,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":5,"e":180}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0"],"code":"Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/0","type":"text","loc":{"start":60695,"end":60703,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":5,"e":13}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","text.0"],"code":"Queries "},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/1","type":"inlineCode","loc":{"start":60703,"end":60725,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":13,"e":35}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.1"],"code":"`cache_ast_lake_nodes`"},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/2","type":"text","loc":{"start":60725,"end":60730,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":35,"e":40}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","text.2"],"code":" via "},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/3","type":"inlineCode","loc":{"start":60730,"end":60756,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":40,"e":66}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.3"],"code":"`cache_ast_lake_nodes_ref`"},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/4","type":"text","loc":{"start":60756,"end":60773,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":66,"e":83}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","text.4"],"code":" for the node by "},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/5","type":"inlineCode","loc":{"start":60773,"end":60794,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":83,"e":104}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.5"],"code":"`r.ref = fragmentUrl`"},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/6","type":"text","loc":{"start":60794,"end":60805,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":104,"e":115}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","text.6"],"code":" — fetches "},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/7","type":"inlineCode","loc":{"start":60805,"end":60842,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":115,"e":152}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","inlineCode.7"],"code":"`n.id, r.ref, n.body, n.num1, n.num2`"},{"id":"/root/children/197/children/1/children/1/children/1/children/0/children/8","type":"text","loc":{"start":60842,"end":60870,"line":{"s":999,"e":999,"code":["  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)"]},"column":{"s":152,"e":180}},"dim":["","list.197","listItem.1","list.1","listItem.1","paragraph.0","text.8"],"code":" (num2 is heading level 1–6)"},{"id":"/root/children/197/children/1/children/1/children/2","type":"listItem","loc":{"start":60873,"end":60960,"line":{"s":1000,"e":1000,"code":["  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"]},"column":{"s":2,"e":89}},"dim":["","list.197","listItem.1","list.1","listItem.2"],"code":"3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"},{"id":"/root/children/197/children/1/children/1/children/2/children/0","type":"paragraph","loc":{"start":60876,"end":60960,"line":{"s":1000,"e":1000,"code":["  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"]},"column":{"s":5,"e":89}},"dim":["","list.197","listItem.1","list.1","listItem.2","paragraph.0"],"code":"Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"},{"id":"/root/children/197/children/1/children/1/children/2/children/0/children/0","type":"text","loc":{"start":60876,"end":60885,"line":{"s":1000,"e":1000,"code":["  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"]},"column":{"s":5,"e":14}},"dim":["","list.197","listItem.1","list.1","listItem.2","paragraph.0","text.0"],"code":"Extracts "},{"id":"/root/children/197/children/1/children/1/children/2/children/0/children/1","type":"inlineCode","loc":{"start":60885,"end":60889,"line":{"s":1000,"e":1000,"code":["  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"]},"column":{"s":14,"e":18}},"dim":["","list.197","listItem.1","list.1","listItem.2","paragraph.0","inlineCode.1"],"code":"`id`"},{"id":"/root/children/197/children/1/children/1/children/2/children/0/children/2","type":"text","loc":{"start":60889,"end":60931,"line":{"s":1000,"e":1000,"code":["  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"]},"column":{"s":18,"e":60}},"dim":["","list.197","listItem.1","list.1","listItem.2","paragraph.0","text.2"],"code":" (full filesystem path + line number) and "},{"id":"/root/children/197/children/1/children/1/children/2/children/0/children/3","type":"inlineCode","loc":{"start":60931,"end":60937,"line":{"s":1000,"e":1000,"code":["  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"]},"column":{"s":60,"e":66}},"dim":["","list.197","listItem.1","list.1","listItem.2","paragraph.0","inlineCode.3"],"code":"`num1`"},{"id":"/root/children/197/children/1/children/1/children/2/children/0/children/4","type":"text","loc":{"start":60937,"end":60960,"line":{"s":1000,"e":1000,"code":["  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)"]},"column":{"s":66,"e":89}},"dim":["","list.197","listItem.1","list.1","listItem.2","paragraph.0","text.4"],"code":" (line number fallback)"},{"id":"/root/children/197/children/1/children/1/children/3","type":"listItem","loc":{"start":60963,"end":61044,"line":{"s":1001,"e":1001,"code":["  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"]},"column":{"s":2,"e":83}},"dim":["","list.197","listItem.1","list.1","listItem.3"],"code":"4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"},{"id":"/root/children/197/children/1/children/1/children/3/children/0","type":"paragraph","loc":{"start":60966,"end":61044,"line":{"s":1001,"e":1001,"code":["  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"]},"column":{"s":5,"e":83}},"dim":["","list.197","listItem.1","list.1","listItem.3","paragraph.0"],"code":"Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"},{"id":"/root/children/197/children/1/children/1/children/3/children/0/children/0","type":"text","loc":{"start":60966,"end":60974,"line":{"s":1001,"e":1001,"code":["  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"]},"column":{"s":5,"e":13}},"dim":["","list.197","listItem.1","list.1","listItem.3","paragraph.0","text.0"],"code":"Derives "},{"id":"/root/children/197/children/1/children/1/children/3/children/0/children/1","type":"inlineCode","loc":{"start":60974,"end":60986,"line":{"s":1001,"e":1001,"code":["  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"]},"column":{"s":13,"e":25}},"dim":["","list.197","listItem.1","list.1","listItem.3","paragraph.0","inlineCode.1"],"code":"`pathInRepo`"},{"id":"/root/children/197/children/1/children/1/children/3/children/0/children/2","type":"text","loc":{"start":60986,"end":61003,"line":{"s":1001,"e":1001,"code":["  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"]},"column":{"s":25,"e":42}},"dim":["","list.197","listItem.1","list.1","listItem.3","paragraph.0","text.2"],"code":" by splitting on "},{"id":"/root/children/197/children/1/children/1/children/3/children/0/children/3","type":"inlineCode","loc":{"start":61003,"end":61018,"line":{"s":1001,"e":1001,"code":["  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"]},"column":{"s":42,"e":57}},"dim":["","list.197","listItem.1","list.1","listItem.3","paragraph.0","inlineCode.3"],"code":"`/repo/<name>/`"},{"id":"/root/children/197/children/1/children/1/children/3/children/0/children/4","type":"text","loc":{"start":61018,"end":61044,"line":{"s":1001,"e":1001,"code":["  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix"]},"column":{"s":57,"e":83}},"dim":["","list.197","listItem.1","list.1","listItem.3","paragraph.0","text.4"],"code":" and stripping line suffix"},{"id":"/root/children/197/children/1/children/1/children/4","type":"listItem","loc":{"start":61047,"end":61416,"line":{"s":1002,"e":1004,"code":["  5. Determines range end:","     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content","     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":2,"e":229}},"dim":["","list.197","listItem.1","list.1","listItem.4"],"code":"5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"},{"id":"/root/children/197/children/1/children/1/children/4/children/0","type":"paragraph","loc":{"start":61050,"end":61071,"line":{"s":1002,"e":1002,"code":["  5. Determines range end:"]},"column":{"s":5,"e":26}},"dim":["","list.197","listItem.1","list.1","listItem.4","paragraph.0"],"code":"Determines range end:"},{"id":"/root/children/197/children/1/children/1/children/4/children/0/children/0","type":"text","loc":{"start":61050,"end":61071,"line":{"s":1002,"e":1002,"code":["  5. Determines range end:"]},"column":{"s":5,"e":26}},"dim":["","list.197","listItem.1","list.1","listItem.4","paragraph.0","text.0"],"code":"Determines range end:"},{"id":"/root/children/197/children/1/children/1/children/4/children/1","type":"list","loc":{"start":61077,"end":61416,"line":{"s":1003,"e":1004,"code":["     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content","     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":5,"e":229}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1"],"code":"- `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary","symbName":"list","symbRange":[null,null],"symbRangeL":[1003,1004],"outerCode":"","outerHtml":""},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/0","type":"listItem","loc":{"start":61077,"end":61186,"line":{"s":1003,"e":1003,"code":["     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content"]},"column":{"s":5,"e":114}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.0"],"code":"- `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/0/children/0","type":"paragraph","loc":{"start":61079,"end":61186,"line":{"s":1003,"e":1003,"code":["     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content"]},"column":{"s":7,"e":114}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.0","paragraph.0"],"code":"`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/0/children/0/children/0","type":"inlineCode","loc":{"start":61079,"end":61091,"line":{"s":1003,"e":1003,"code":["     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content"]},"column":{"s":7,"e":19}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.0","paragraph.0","inlineCode.0"],"code":"`deep=false`"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/0/children/0/children/1","type":"text","loc":{"start":61091,"end":61186,"line":{"s":1003,"e":1003,"code":["     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content"]},"column":{"s":19,"e":114}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.0","paragraph.0","text.1"],"code":": SQL query finds the very next heading in the file — returns just the heading's direct content"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1","type":"listItem","loc":{"start":61192,"end":61416,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":5,"e":229}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1"],"code":"- `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1/children/0","type":"paragraph","loc":{"start":61194,"end":61416,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":7,"e":229}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1","paragraph.0"],"code":"`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1/children/0/children/0","type":"inlineCode","loc":{"start":61194,"end":61205,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":7,"e":18}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1","paragraph.0","inlineCode.0"],"code":"`deep=true`"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1/children/0/children/1","type":"text","loc":{"start":61205,"end":61308,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":18,"e":121}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1","paragraph.0","text.1"],"code":": scans file content from heading line forward, finds the next heading at same or higher level (parses "},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1/children/0/children/2","type":"inlineCode","loc":{"start":61308,"end":61311,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":121,"e":124}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1","paragraph.0","inlineCode.2"],"code":"`#`"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1/children/0/children/3","type":"text","loc":{"start":61311,"end":61372,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":124,"e":185}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1","paragraph.0","text.3"],"code":" markers directly from text) — includes all nested children; "},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1/children/0/children/4","type":"inlineCode","loc":{"start":61372,"end":61381,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":185,"e":194}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1","paragraph.0","inlineCode.4"],"code":"`endLine`"},{"id":"/root/children/197/children/1/children/1/children/4/children/1/children/1/children/0/children/5","type":"text","loc":{"start":61381,"end":61416,"line":{"s":1004,"e":1004,"code":["     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary"]},"column":{"s":194,"e":229}},"dim":["","list.197","listItem.1","list.1","listItem.4","list.1","listItem.1","paragraph.0","text.5"],"code":" reflects the full subtree boundary"},{"id":"/root/children/197/children/1/children/1/children/5","type":"listItem","loc":{"start":61419,"end":61576,"line":{"s":1005,"e":1005,"code":["  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"]},"column":{"s":2,"e":159}},"dim":["","list.197","listItem.1","list.1","listItem.5"],"code":"6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"},{"id":"/root/children/197/children/1/children/1/children/5/children/0","type":"paragraph","loc":{"start":61422,"end":61576,"line":{"s":1005,"e":1005,"code":["  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"]},"column":{"s":5,"e":159}},"dim":["","list.197","listItem.1","list.1","listItem.5","paragraph.0"],"code":"Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"},{"id":"/root/children/197/children/1/children/1/children/5/children/0/children/0","type":"text","loc":{"start":61422,"end":61447,"line":{"s":1005,"e":1005,"code":["  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"]},"column":{"s":5,"e":30}},"dim":["","list.197","listItem.1","list.1","listItem.5","paragraph.0","text.0"],"code":"Fetches file content via "},{"id":"/root/children/197/children/1/children/1/children/5/children/0/children/1","type":"inlineCode","loc":{"start":61447,"end":61501,"line":{"s":1005,"e":1005,"code":["  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"]},"column":{"s":30,"e":84}},"dim":["","list.197","listItem.1","list.1","listItem.5","paragraph.0","inlineCode.1"],"code":"`ssss.fetchWithETag(fetchUrl, { token: _getToken() })`"},{"id":"/root/children/197/children/1/children/1/children/5/children/0/children/2","type":"text","loc":{"start":61501,"end":61520,"line":{"s":1005,"e":1005,"code":["  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"]},"column":{"s":84,"e":103}},"dim":["","list.197","listItem.1","list.1","listItem.5","paragraph.0","text.2"],"code":" where fetchUrl is "},{"id":"/root/children/197/children/1/children/1/children/5/children/0/children/3","type":"inlineCode","loc":{"start":61520,"end":61532,"line":{"s":1005,"e":1005,"code":["  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"]},"column":{"s":103,"e":115}},"dim":["","list.197","listItem.1","list.1","listItem.5","paragraph.0","inlineCode.3"],"code":"`ssss.Url(\\`"},{"id":"/root/children/197/children/1/children/1/children/5/children/0/children/4","type":"text","loc":{"start":61532,"end":61576,"line":{"s":1005,"e":1005,"code":["  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"]},"column":{"s":115,"e":159}},"dim":["","list.197","listItem.1","list.1","listItem.5","paragraph.0","text.4"],"code":"${repoUrlBase}/${repoName}/${pathInRepo}\\`)`"},{"id":"/root/children/197/children/1/children/1/children/6","type":"listItem","loc":{"start":61579,"end":61630,"line":{"s":1006,"e":1006,"code":["  7. Slices `lines[start..end]` (start = lineNum - 1)"]},"column":{"s":2,"e":53}},"dim":["","list.197","listItem.1","list.1","listItem.6"],"code":"7. Slices `lines[start..end]` (start = lineNum - 1)"},{"id":"/root/children/197/children/1/children/1/children/6/children/0","type":"paragraph","loc":{"start":61582,"end":61630,"line":{"s":1006,"e":1006,"code":["  7. Slices `lines[start..end]` (start = lineNum - 1)"]},"column":{"s":5,"e":53}},"dim":["","list.197","listItem.1","list.1","listItem.6","paragraph.0"],"code":"Slices `lines[start..end]` (start = lineNum - 1)"},{"id":"/root/children/197/children/1/children/1/children/6/children/0/children/0","type":"text","loc":{"start":61582,"end":61589,"line":{"s":1006,"e":1006,"code":["  7. Slices `lines[start..end]` (start = lineNum - 1)"]},"column":{"s":5,"e":12}},"dim":["","list.197","listItem.1","list.1","listItem.6","paragraph.0","text.0"],"code":"Slices "},{"id":"/root/children/197/children/1/children/1/children/6/children/0/children/1","type":"inlineCode","loc":{"start":61589,"end":61608,"line":{"s":1006,"e":1006,"code":["  7. Slices `lines[start..end]` (start = lineNum - 1)"]},"column":{"s":12,"e":31}},"dim":["","list.197","listItem.1","list.1","listItem.6","paragraph.0","inlineCode.1"],"code":"`lines[start..end]`"},{"id":"/root/children/197/children/1/children/1/children/6/children/0/children/2","type":"text","loc":{"start":61608,"end":61630,"line":{"s":1006,"e":1006,"code":["  7. Slices `lines[start..end]` (start = lineNum - 1)"]},"column":{"s":31,"e":53}},"dim":["","list.197","listItem.1","list.1","listItem.6","paragraph.0","text.2"],"code":" (start = lineNum - 1)"},{"id":"/root/children/197/children/1/children/1/children/7","type":"listItem","loc":{"start":61633,"end":61696,"line":{"s":1007,"e":1007,"code":["  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":2,"e":65}},"dim":["","list.197","listItem.1","list.1","listItem.7"],"code":"8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"},{"id":"/root/children/197/children/1/children/1/children/7/children/0","type":"paragraph","loc":{"start":61636,"end":61696,"line":{"s":1007,"e":1007,"code":["  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":5,"e":65}},"dim":["","list.197","listItem.1","list.1","listItem.7","paragraph.0"],"code":"Returns `{ text, pathInRepo, startLine, endLine }` or `null`"},{"id":"/root/children/197/children/1/children/1/children/7/children/0/children/0","type":"text","loc":{"start":61636,"end":61644,"line":{"s":1007,"e":1007,"code":["  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":5,"e":13}},"dim":["","list.197","listItem.1","list.1","listItem.7","paragraph.0","text.0"],"code":"Returns "},{"id":"/root/children/197/children/1/children/1/children/7/children/0/children/1","type":"inlineCode","loc":{"start":61644,"end":61686,"line":{"s":1007,"e":1007,"code":["  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":13,"e":55}},"dim":["","list.197","listItem.1","list.1","listItem.7","paragraph.0","inlineCode.1"],"code":"`{ text, pathInRepo, startLine, endLine }`"},{"id":"/root/children/197/children/1/children/1/children/7/children/0/children/2","type":"text","loc":{"start":61686,"end":61690,"line":{"s":1007,"e":1007,"code":["  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":55,"e":59}},"dim":["","list.197","listItem.1","list.1","listItem.7","paragraph.0","text.2"],"code":" or "},{"id":"/root/children/197/children/1/children/1/children/7/children/0/children/3","type":"inlineCode","loc":{"start":61690,"end":61696,"line":{"s":1007,"e":1007,"code":["  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`"]},"column":{"s":59,"e":65}},"dim":["","list.197","listItem.1","list.1","listItem.7","paragraph.0","inlineCode.3"],"code":"`null`"},{"id":"/root/children/197/children/2","type":"listItem","loc":{"start":61698,"end":61911,"line":{"s":1009,"e":1009,"code":["- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."]},"column":{"s":0,"e":213}},"dim":["","list.197","listItem.2"],"code":"- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."},{"id":"/root/children/197/children/2/children/0","type":"paragraph","loc":{"start":61700,"end":61911,"line":{"s":1009,"e":1009,"code":["- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."]},"column":{"s":2,"e":213}},"dim":["","list.197","listItem.2","paragraph.0"],"code":"`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."},{"id":"/root/children/197/children/2/children/0/children/0","type":"inlineCode","loc":{"start":61700,"end":61736,"line":{"s":1009,"e":1009,"code":["- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."]},"column":{"s":2,"e":38}},"dim":["","list.197","listItem.2","paragraph.0","inlineCode.0"],"code":"`relevelFragment(text, cursorLevel)`"},{"id":"/root/children/197/children/2/children/0/children/1","type":"text","loc":{"start":61736,"end":61816,"line":{"s":1009,"e":1009,"code":["- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."]},"column":{"s":38,"e":118}},"dim":["","list.197","listItem.2","paragraph.0","text.1"],"code":" — pure: shifts all heading levels in fragment text so its root heading becomes "},{"id":"/root/children/197/children/2/children/0/children/2","type":"inlineCode","loc":{"start":61816,"end":61833,"line":{"s":1009,"e":1009,"code":["- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."]},"column":{"s":118,"e":135}},"dim":["","list.197","listItem.2","paragraph.0","inlineCode.2"],"code":"`cursorLevel + 1`"},{"id":"/root/children/197/children/2/children/0/children/3","type":"text","loc":{"start":61833,"end":61911,"line":{"s":1009,"e":1009,"code":["- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."]},"column":{"s":135,"e":213}},"dim":["","list.197","listItem.2","paragraph.0","text.3"],"code":". Clamps to 1–6. Returns text unchanged if no headings found or shift is zero."},{"id":"/root/children/198","type":"heading","loc":{"start":61913,"end":61943,"line":{"s":1011,"e":1011,"code":["### replantRef touchbar action"]},"column":{"s":0,"e":30}},"dim":["","heading.198"],"code":"### replantRef touchbar action","symbName":"heading","symbRange":[61945,62819],"symbRangeL":[1011,1024],"outerCode":"\nTriggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:\n\n1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)\n2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`\n\nKey: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.","outerHtml":"\n<p>Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:</p>\n\n<ol><li>Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)</li><li>Checks `route.isInEditor` — only works in edit layer</li><li>Validates via `validateFragmentUrl(t)`</li><li>Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`</li><li>Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line</li><li>Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor</li><li>Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`</li></ol>\n\n<p>Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.</p>"},{"id":"/root/children/198/children/0","type":"text","loc":{"start":61917,"end":61943,"line":{"s":1011,"e":1011,"code":["### replantRef touchbar action"]},"column":{"s":4,"e":30}},"dim":["","heading.198","text.0"],"code":"replantRef touchbar action"},{"id":"/root/children/199","type":"paragraph","loc":{"start":61945,"end":62015,"line":{"s":1013,"e":1013,"code":["Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:"]},"column":{"s":0,"e":70}},"dim":["","paragraph.199"],"code":"Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:"},{"id":"/root/children/199/children/0","type":"text","loc":{"start":61945,"end":61968,"line":{"s":1013,"e":1013,"code":["Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:"]},"column":{"s":0,"e":23}},"dim":["","paragraph.199","text.0"],"code":"Triggered via touchbar "},{"id":"/root/children/199/children/1","type":"inlineCode","loc":{"start":61968,"end":61986,"line":{"s":1013,"e":1013,"code":["Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:"]},"column":{"s":23,"e":41}},"dim":["","paragraph.199","inlineCode.1"],"code":"`paper:replantRef`"},{"id":"/root/children/199/children/2","type":"text","loc":{"start":61986,"end":61990,"line":{"s":1013,"e":1013,"code":["Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:"]},"column":{"s":41,"e":45}},"dim":["","paragraph.199","text.2"],"code":" in "},{"id":"/root/children/199/children/3","type":"inlineCode","loc":{"start":61990,"end":62008,"line":{"s":1013,"e":1013,"code":["Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:"]},"column":{"s":45,"e":63}},"dim":["","paragraph.199","inlineCode.3"],"code":"`window.ctrlPaper`"},{"id":"/root/children/199/children/4","type":"text","loc":{"start":62008,"end":62015,"line":{"s":1013,"e":1013,"code":["Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:"]},"column":{"s":63,"e":70}},"dim":["","paragraph.199","text.4"],"code":". Flow:"},{"id":"/root/children/200","type":"list","loc":{"start":62017,"end":62695,"line":{"s":1015,"e":1021,"code":["1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)","2. Checks `route.isInEditor` — only works in edit layer","3. Validates via `validateFragmentUrl(t)`","4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`","5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line","6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor","7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"]},"column":{"s":0,"e":160}},"dim":["","list.200"],"code":"1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)\n2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`","symbName":"list","symbRange":[62697,63857],"symbRangeL":[1015,1048],"outerCode":"2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`\n\nKey: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.\n\n### URL construction\n\nFollowing `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).\n\n### SQL rows\n\n`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.\n\n## OuchDB (Remote CouchDB/PouchDB)\n\n**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.\n\n**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:\n\n```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```\n\nThe `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).\n\n**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.\n\n**Usage (vegaLinks persistence):**","outerHtml":"<ol><li>Checks `route.isInEditor` — only works in edit layer</li><li>Validates via `validateFragmentUrl(t)`</li><li>Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`</li><li>Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line</li><li>Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor</li><li>Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`</li></ol>\n\n<p>Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.</p>\n\n<h3>URL construction</h3>\n\n<p>Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).</p>\n\n<h3>SQL rows</h3>\n\n<p>`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.</p>\n\n<h2>OuchDB (Remote CouchDB/PouchDB)</h2>\n\n<p><b>Adapter:</b> `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.</p>\n\n<p><b>Wiring:</b> `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:</p>\n\n<p>```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```</p>\n\n<p>The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).</p>\n\n<p><b>Protocol config:</b> `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.</p>\n\n<p><b>Usage (vegaLinks persistence):</b></p>"},{"id":"/root/children/200/children/0","type":"listItem","loc":{"start":62017,"end":62109,"line":{"s":1015,"e":1015,"code":["1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)"]},"column":{"s":0,"e":92}},"dim":["","list.200","listItem.0"],"code":"1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)"},{"id":"/root/children/200/children/0/children/0","type":"paragraph","loc":{"start":62020,"end":62109,"line":{"s":1015,"e":1015,"code":["1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)"]},"column":{"s":3,"e":92}},"dim":["","list.200","listItem.0","paragraph.0"],"code":"Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)"},{"id":"/root/children/200/children/0/children/0/children/0","type":"text","loc":{"start":62020,"end":62068,"line":{"s":1015,"e":1015,"code":["1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)"]},"column":{"s":3,"e":51}},"dim":["","list.200","listItem.0","paragraph.0","text.0"],"code":"Reads clipboard (currently hardcoded test value "},{"id":"/root/children/200/children/0/children/0/children/1","type":"inlineCode","loc":{"start":62068,"end":62108,"line":{"s":1015,"e":1015,"code":["1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)"]},"column":{"s":51,"e":91}},"dim":["","list.200","listItem.0","paragraph.0","inlineCode.1"],"code":"`\"paper/scratchpad.mdd::candidateDruhy\"`"},{"id":"/root/children/200/children/0/children/0/children/2","type":"text","loc":{"start":62108,"end":62109,"line":{"s":1015,"e":1015,"code":["1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)"]},"column":{"s":91,"e":92}},"dim":["","list.200","listItem.0","paragraph.0","text.2"],"code":")"},{"id":"/root/children/200/children/1","type":"listItem","loc":{"start":62110,"end":62165,"line":{"s":1016,"e":1016,"code":["2. Checks `route.isInEditor` — only works in edit layer"]},"column":{"s":0,"e":55}},"dim":["","list.200","listItem.1"],"code":"2. Checks `route.isInEditor` — only works in edit layer"},{"id":"/root/children/200/children/1/children/0","type":"paragraph","loc":{"start":62113,"end":62165,"line":{"s":1016,"e":1016,"code":["2. Checks `route.isInEditor` — only works in edit layer"]},"column":{"s":3,"e":55}},"dim":["","list.200","listItem.1","paragraph.0"],"code":"Checks `route.isInEditor` — only works in edit layer"},{"id":"/root/children/200/children/1/children/0/children/0","type":"text","loc":{"start":62113,"end":62120,"line":{"s":1016,"e":1016,"code":["2. Checks `route.isInEditor` — only works in edit layer"]},"column":{"s":3,"e":10}},"dim":["","list.200","listItem.1","paragraph.0","text.0"],"code":"Checks "},{"id":"/root/children/200/children/1/children/0/children/1","type":"inlineCode","loc":{"start":62120,"end":62138,"line":{"s":1016,"e":1016,"code":["2. Checks `route.isInEditor` — only works in edit layer"]},"column":{"s":10,"e":28}},"dim":["","list.200","listItem.1","paragraph.0","inlineCode.1"],"code":"`route.isInEditor`"},{"id":"/root/children/200/children/1/children/0/children/2","type":"text","loc":{"start":62138,"end":62165,"line":{"s":1016,"e":1016,"code":["2. Checks `route.isInEditor` — only works in edit layer"]},"column":{"s":28,"e":55}},"dim":["","list.200","listItem.1","paragraph.0","text.2"],"code":" — only works in edit layer"},{"id":"/root/children/200/children/2","type":"listItem","loc":{"start":62166,"end":62207,"line":{"s":1017,"e":1017,"code":["3. Validates via `validateFragmentUrl(t)`"]},"column":{"s":0,"e":41}},"dim":["","list.200","listItem.2"],"code":"3. Validates via `validateFragmentUrl(t)`"},{"id":"/root/children/200/children/2/children/0","type":"paragraph","loc":{"start":62169,"end":62207,"line":{"s":1017,"e":1017,"code":["3. Validates via `validateFragmentUrl(t)`"]},"column":{"s":3,"e":41}},"dim":["","list.200","listItem.2","paragraph.0"],"code":"Validates via `validateFragmentUrl(t)`"},{"id":"/root/children/200/children/2/children/0/children/0","type":"text","loc":{"start":62169,"end":62183,"line":{"s":1017,"e":1017,"code":["3. Validates via `validateFragmentUrl(t)`"]},"column":{"s":3,"e":17}},"dim":["","list.200","listItem.2","paragraph.0","text.0"],"code":"Validates via "},{"id":"/root/children/200/children/2/children/0/children/1","type":"inlineCode","loc":{"start":62183,"end":62207,"line":{"s":1017,"e":1017,"code":["3. Validates via `validateFragmentUrl(t)`"]},"column":{"s":17,"e":41}},"dim":["","list.200","listItem.2","paragraph.0","inlineCode.1"],"code":"`validateFragmentUrl(t)`"},{"id":"/root/children/200/children/3","type":"listItem","loc":{"start":62208,"end":62285,"line":{"s":1018,"e":1018,"code":["4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`"]},"column":{"s":0,"e":77}},"dim":["","list.200","listItem.3"],"code":"4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`"},{"id":"/root/children/200/children/3/children/0","type":"paragraph","loc":{"start":62211,"end":62285,"line":{"s":1018,"e":1018,"code":["4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`"]},"column":{"s":3,"e":77}},"dim":["","list.200","listItem.3","paragraph.0"],"code":"Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`"},{"id":"/root/children/200/children/3/children/0/children/0","type":"text","loc":{"start":62211,"end":62230,"line":{"s":1018,"e":1018,"code":["4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`"]},"column":{"s":3,"e":22}},"dim":["","list.200","listItem.3","paragraph.0","text.0"],"code":"Loads fragment via "},{"id":"/root/children/200/children/3/children/0/children/1","type":"inlineCode","loc":{"start":62230,"end":62285,"line":{"s":1018,"e":1018,"code":["4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`"]},"column":{"s":22,"e":77}},"dim":["","list.200","listItem.3","paragraph.0","inlineCode.1"],"code":"`loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`"},{"id":"/root/children/200/children/4","type":"listItem","loc":{"start":62286,"end":62421,"line":{"s":1019,"e":1019,"code":["5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"]},"column":{"s":0,"e":135}},"dim":["","list.200","listItem.4"],"code":"5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"},{"id":"/root/children/200/children/4/children/0","type":"paragraph","loc":{"start":62289,"end":62421,"line":{"s":1019,"e":1019,"code":["5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"]},"column":{"s":3,"e":135}},"dim":["","list.200","listItem.4","paragraph.0"],"code":"Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"},{"id":"/root/children/200/children/4/children/0/children/0","type":"text","loc":{"start":62289,"end":62328,"line":{"s":1019,"e":1019,"code":["5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"]},"column":{"s":3,"e":42}},"dim":["","list.200","listItem.4","paragraph.0","text.0"],"code":"Determines heading level at cursor via "},{"id":"/root/children/200/children/4/children/0/children/1","type":"inlineCode","loc":{"start":62328,"end":62362,"line":{"s":1019,"e":1019,"code":["5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"]},"column":{"s":42,"e":76}},"dim":["","list.200","listItem.4","paragraph.0","inlineCode.1"],"code":"`getCursorHeadingLevel(theEditor)`"},{"id":"/root/children/200/children/4/children/0/children/2","type":"text","loc":{"start":62362,"end":62405,"line":{"s":1019,"e":1019,"code":["5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"]},"column":{"s":76,"e":119}},"dim":["","list.200","listItem.4","paragraph.0","text.2"],"code":" — scans backwards from cursor for nearest "},{"id":"/root/children/200/children/4/children/0/children/3","type":"inlineCode","loc":{"start":62405,"end":62408,"line":{"s":1019,"e":1019,"code":["5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"]},"column":{"s":119,"e":122}},"dim":["","list.200","listItem.4","paragraph.0","inlineCode.3"],"code":"`#`"},{"id":"/root/children/200/children/4/children/0/children/4","type":"text","loc":{"start":62408,"end":62421,"line":{"s":1019,"e":1019,"code":["5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line"]},"column":{"s":122,"e":135}},"dim":["","list.200","listItem.4","paragraph.0","text.4"],"code":" heading line"},{"id":"/root/children/200/children/5","type":"listItem","loc":{"start":62422,"end":62534,"line":{"s":1020,"e":1020,"code":["6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor"]},"column":{"s":0,"e":112}},"dim":["","list.200","listItem.5"],"code":"6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor"},{"id":"/root/children/200/children/5/children/0","type":"paragraph","loc":{"start":62425,"end":62534,"line":{"s":1020,"e":1020,"code":["6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor"]},"column":{"s":3,"e":112}},"dim":["","list.200","listItem.5","paragraph.0"],"code":"Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor"},{"id":"/root/children/200/children/5/children/0/children/0","type":"text","loc":{"start":62425,"end":62433,"line":{"s":1020,"e":1020,"code":["6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor"]},"column":{"s":3,"e":11}},"dim":["","list.200","listItem.5","paragraph.0","text.0"],"code":"Applies "},{"id":"/root/children/200/children/5/children/0/children/1","type":"inlineCode","loc":{"start":62433,"end":62478,"line":{"s":1020,"e":1020,"code":["6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor"]},"column":{"s":11,"e":56}},"dim":["","list.200","listItem.5","paragraph.0","inlineCode.1"],"code":"`relevelFragment(fragment.text, cursorLevel)`"},{"id":"/root/children/200/children/5/children/0/children/2","type":"text","loc":{"start":62478,"end":62534,"line":{"s":1020,"e":1020,"code":["6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor"]},"column":{"s":56,"e":112}},"dim":["","list.200","listItem.5","paragraph.0","text.2"],"code":" to shift the fragment's heading levels under the cursor"},{"id":"/root/children/200/children/6","type":"listItem","loc":{"start":62535,"end":62695,"line":{"s":1021,"e":1021,"code":["7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"]},"column":{"s":0,"e":160}},"dim":["","list.200","listItem.6"],"code":"7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"},{"id":"/root/children/200/children/6/children/0","type":"paragraph","loc":{"start":62538,"end":62695,"line":{"s":1021,"e":1021,"code":["7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"]},"column":{"s":3,"e":160}},"dim":["","list.200","listItem.6","paragraph.0"],"code":"Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"},{"id":"/root/children/200/children/6/children/0/children/0","type":"text","loc":{"start":62538,"end":62580,"line":{"s":1021,"e":1021,"code":["7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"]},"column":{"s":3,"e":45}},"dim":["","list.200","listItem.6","paragraph.0","text.0"],"code":"Inserts adjusted text into CM6 editor via "},{"id":"/root/children/200/children/6/children/0/children/1","type":"inlineCode","loc":{"start":62580,"end":62695,"line":{"s":1021,"e":1021,"code":["7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"]},"column":{"s":45,"e":160}},"dim":["","list.200","listItem.6","paragraph.0","inlineCode.1"],"code":"`window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`"},{"id":"/root/children/201","type":"paragraph","loc":{"start":62697,"end":62819,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":0,"e":122}},"dim":["","paragraph.201"],"code":"Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."},{"id":"/root/children/201/children/0","type":"text","loc":{"start":62697,"end":62702,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":0,"e":5}},"dim":["","paragraph.201","text.0"],"code":"Key: "},{"id":"/root/children/201/children/1","type":"inlineCode","loc":{"start":62702,"end":62720,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":5,"e":23}},"dim":["","paragraph.201","inlineCode.1"],"code":"`window.theEditor`"},{"id":"/root/children/201/children/2","type":"text","loc":{"start":62720,"end":62728,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":23,"e":31}},"dim":["","paragraph.201","text.2"],"code":" is the "},{"id":"/root/children/201/children/3","type":"inlineCode","loc":{"start":62728,"end":62742,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":31,"e":45}},"dim":["","paragraph.201","inlineCode.3"],"code":"`initEditor()`"},{"id":"/root/children/201/children/4","type":"text","loc":{"start":62742,"end":62766,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":45,"e":69}},"dim":["","paragraph.201","text.4"],"code":" return object. Raw CM6 "},{"id":"/root/children/201/children/5","type":"inlineCode","loc":{"start":62766,"end":62778,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":69,"e":81}},"dim":["","paragraph.201","inlineCode.5"],"code":"`EditorView`"},{"id":"/root/children/201/children/6","type":"text","loc":{"start":62778,"end":62785,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":81,"e":88}},"dim":["","paragraph.201","text.6"],"code":" is at "},{"id":"/root/children/201/children/7","type":"inlineCode","loc":{"start":62785,"end":62818,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":88,"e":121}},"dim":["","paragraph.201","inlineCode.7"],"code":"`window.theEditor.panes.one.view`"},{"id":"/root/children/201/children/8","type":"text","loc":{"start":62818,"end":62819,"line":{"s":1023,"e":1023,"code":["Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`."]},"column":{"s":121,"e":122}},"dim":["","paragraph.201","text.8"],"code":"."},{"id":"/root/children/202","type":"heading","loc":{"start":62821,"end":62841,"line":{"s":1025,"e":1025,"code":["### URL construction"]},"column":{"s":0,"e":20}},"dim":["","heading.202"],"code":"### URL construction","symbName":"heading","symbRange":[62843,63065],"symbRangeL":[1025,1028],"outerCode":"\nFollowing `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).","outerHtml":"\n<p>Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).</p>"},{"id":"/root/children/202/children/0","type":"text","loc":{"start":62825,"end":62841,"line":{"s":1025,"e":1025,"code":["### URL construction"]},"column":{"s":4,"e":20}},"dim":["","heading.202","text.0"],"code":"URL construction"},{"id":"/root/children/203","type":"paragraph","loc":{"start":62843,"end":63065,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":0,"e":222}},"dim":["","paragraph.203"],"code":"Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."},{"id":"/root/children/203/children/0","type":"text","loc":{"start":62843,"end":62853,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":0,"e":10}},"dim":["","paragraph.203","text.0"],"code":"Following "},{"id":"/root/children/203/children/1","type":"inlineCode","loc":{"start":62853,"end":62868,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":10,"e":25}},"dim":["","paragraph.203","inlineCode.1"],"code":"`tokensToPaths`"},{"id":"/root/children/203/children/2","type":"text","loc":{"start":62868,"end":62878,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":25,"e":35}},"dim":["","paragraph.203","text.2"],"code":" pattern: "},{"id":"/root/children/203/children/3","type":"inlineCode","loc":{"start":62878,"end":62890,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":35,"e":47}},"dim":["","paragraph.203","inlineCode.3"],"code":"`ssss.Url(\\`"},{"id":"/root/children/203/children/4","type":"text","loc":{"start":62890,"end":62933,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":47,"e":90}},"dim":["","paragraph.203","text.4"],"code":"${repoUrlBase}/${repoName}/${pathInRepo}\\`)"},{"id":"/root/children/203/children/5","type":"inlineCode","loc":{"start":62933,"end":62955,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":90,"e":112}},"dim":["","paragraph.203","inlineCode.5"],"code":"`. Data server (e.g. `"},{"id":"/root/children/203/children/6","type":"text","loc":{"start":62955,"end":62972,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":112,"e":129}},"dim":["","paragraph.203","text.6"],"code":"repo.untiloop.loc"},{"id":"/root/children/203/children/7","type":"inlineCode","loc":{"start":62972,"end":62992,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":129,"e":149}},"dim":["","paragraph.203","inlineCode.7"],"code":"`) serves files at `"},{"id":"/root/children/203/children/8","type":"text","loc":{"start":62992,"end":62993,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":149,"e":150}},"dim":["","paragraph.203","text.8"],"code":"/"},{"id":"/root/children/203/children/9","type":"html","loc":{"start":62993,"end":63003,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":150,"e":160}},"dim":["","paragraph.203","html.9"],"code":"<repoName>"},{"id":"/root/children/203/children/10","type":"text","loc":{"start":63003,"end":63004,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":160,"e":161}},"dim":["","paragraph.203","text.10"],"code":"/"},{"id":"/root/children/203/children/11","type":"html","loc":{"start":63004,"end":63010,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":161,"e":167}},"dim":["","paragraph.203","html.11"],"code":"<path>"},{"id":"/root/children/203/children/12","type":"inlineCode","loc":{"start":63010,"end":63041,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":167,"e":198}},"dim":["","paragraph.203","inlineCode.12"],"code":"` — separate from app server (`"},{"id":"/root/children/203/children/13","type":"text","loc":{"start":63041,"end":63065,"line":{"s":1027,"e":1027,"code":["Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`)."]},"column":{"s":198,"e":222}},"dim":["","paragraph.203","text.13"],"code":"repolero.untiloop.loc`)."},{"id":"/root/children/204","type":"heading","loc":{"start":63067,"end":63079,"line":{"s":1029,"e":1029,"code":["### SQL rows"]},"column":{"s":0,"e":12}},"dim":["","heading.204"],"code":"### SQL rows","symbName":"heading","symbRange":[63081,63206],"symbRangeL":[1029,1032],"outerCode":"\n`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.","outerHtml":"\n<p>`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.</p>"},{"id":"/root/children/204/children/0","type":"text","loc":{"start":63071,"end":63079,"line":{"s":1029,"e":1029,"code":["### SQL rows"]},"column":{"s":4,"e":12}},"dim":["","heading.204","text.0"],"code":"SQL rows"},{"id":"/root/children/205","type":"paragraph","loc":{"start":63081,"end":63206,"line":{"s":1031,"e":1031,"code":["`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure."]},"column":{"s":0,"e":125}},"dim":["","paragraph.205"],"code":"`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure."},{"id":"/root/children/205/children/0","type":"inlineCode","loc":{"start":63081,"end":63114,"line":{"s":1031,"e":1031,"code":["`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure."]},"column":{"s":0,"e":33}},"dim":["","paragraph.205","inlineCode.0"],"code":"`STATE.remoteSqlite.execSql(sql)`"},{"id":"/root/children/205/children/1","type":"text","loc":{"start":63114,"end":63123,"line":{"s":1031,"e":1031,"code":["`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure."]},"column":{"s":33,"e":42}},"dim":["","paragraph.205","text.1"],"code":" returns "},{"id":"/root/children/205/children/2","type":"inlineCode","loc":{"start":63123,"end":63159,"line":{"s":1031,"e":1031,"code":["`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure."]},"column":{"s":42,"e":78}},"dim":["","paragraph.205","inlineCode.2"],"code":"`{ rows: [[col1, col2, ...], ...] }`"},{"id":"/root/children/205/children/3","type":"text","loc":{"start":63159,"end":63206,"line":{"s":1031,"e":1031,"code":["`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure."]},"column":{"s":78,"e":125}},"dim":["","paragraph.205","text.3"],"code":". Destructure by index, not object destructure."},{"id":"/root/children/206","type":"heading","loc":{"start":63208,"end":63242,"line":{"s":1033,"e":1033,"code":["## OuchDB (Remote CouchDB/PouchDB)"]},"column":{"s":0,"e":34}},"dim":["","heading.206"],"code":"## OuchDB (Remote CouchDB/PouchDB)","symbName":"heading","symbRange":[63244,64526],"symbRangeL":[1033,1057],"outerCode":"\n**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.\n\n**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:\n\n```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```\n\nThe `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).\n\n**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.\n\n**Usage (vegaLinks persistence):**\n\n- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array\n- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`\n- Guard: `if (!STATE.remoteOuchdb) return`\n- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)\n\n**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).\n\n**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.","outerHtml":"\n<p><b>Adapter:</b> `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.</p>\n\n<p><b>Wiring:</b> `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:</p>\n\n<p>```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```</p>\n\n<p>The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).</p>\n\n<p><b>Protocol config:</b> `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.</p>\n\n<p><b>Usage (vegaLinks persistence):</b></p>\n\n<ul><li>`loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array</li><li>`saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`</li><li>Guard: `if (!STATE.remoteOuchdb) return`</li><li>Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)</li></ul>\n\n<p><b>Adapter API:</b> `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).</p>\n\n<p><b>`update()` method:</b> GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.</p>"},{"id":"/root/children/206/children/0","type":"text","loc":{"start":63211,"end":63242,"line":{"s":1033,"e":1033,"code":["## OuchDB (Remote CouchDB/PouchDB)"]},"column":{"s":3,"e":34}},"dim":["","heading.206","text.0"],"code":"OuchDB (Remote CouchDB/PouchDB)"},{"id":"/root/children/207","type":"paragraph","loc":{"start":63244,"end":63356,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":0,"e":112}},"dim":["","paragraph.207"],"code":"**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."},{"id":"/root/children/207/children/0","type":"strong","loc":{"start":63244,"end":63256,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":0,"e":12}},"dim":["","paragraph.207","strong.0"],"code":"**Adapter:**"},{"id":"/root/children/207/children/0/children/0","type":"text","loc":{"start":63246,"end":63254,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":2,"e":10}},"dim":["","paragraph.207","strong.0","text.0"],"code":"Adapter:"},{"id":"/root/children/207/children/1","type":"text","loc":{"start":63256,"end":63257,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":12,"e":13}},"dim":["","paragraph.207","text.1"],"code":" "},{"id":"/root/children/207/children/2","type":"inlineCode","loc":{"start":63257,"end":63301,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":13,"e":57}},"dim":["","paragraph.207","inlineCode.2"],"code":"`source/assets/s4/adapters/remote-ouchdb.js`"},{"id":"/root/children/207/children/3","type":"text","loc":{"start":63301,"end":63304,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":57,"e":60}},"dim":["","paragraph.207","text.3"],"code":" — "},{"id":"/root/children/207/children/4","type":"inlineCode","loc":{"start":63304,"end":63318,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":60,"e":74}},"dim":["","paragraph.207","inlineCode.4"],"code":"`RemoteOuchDb`"},{"id":"/root/children/207/children/5","type":"text","loc":{"start":63318,"end":63336,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":74,"e":92}},"dim":["","paragraph.207","text.5"],"code":" class exposed as "},{"id":"/root/children/207/children/6","type":"inlineCode","loc":{"start":63336,"end":63355,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":92,"e":111}},"dim":["","paragraph.207","inlineCode.6"],"code":"`ssss.RemoteOuchdb`"},{"id":"/root/children/207/children/7","type":"text","loc":{"start":63355,"end":63356,"line":{"s":1035,"e":1035,"code":["**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`."]},"column":{"s":111,"e":112}},"dim":["","paragraph.207","text.7"],"code":"."},{"id":"/root/children/208","type":"paragraph","loc":{"start":63358,"end":63501,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":0,"e":143}},"dim":["","paragraph.208"],"code":"**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"},{"id":"/root/children/208/children/0","type":"strong","loc":{"start":63358,"end":63369,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":0,"e":11}},"dim":["","paragraph.208","strong.0"],"code":"**Wiring:**"},{"id":"/root/children/208/children/0/children/0","type":"text","loc":{"start":63360,"end":63367,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":2,"e":9}},"dim":["","paragraph.208","strong.0","text.0"],"code":"Wiring:"},{"id":"/root/children/208/children/1","type":"text","loc":{"start":63369,"end":63370,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":11,"e":12}},"dim":["","paragraph.208","text.1"],"code":" "},{"id":"/root/children/208/children/2","type":"inlineCode","loc":{"start":63370,"end":63383,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":12,"e":25}},"dim":["","paragraph.208","inlineCode.2"],"code":"`dbOuchdbURL`"},{"id":"/root/children/208/children/3","type":"text","loc":{"start":63383,"end":63401,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":25,"e":43}},"dim":["","paragraph.208","text.3"],"code":" is resolved from "},{"id":"/root/children/208/children/4","type":"inlineCode","loc":{"start":63401,"end":63419,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":43,"e":61}},"dim":["","paragraph.208","inlineCode.4"],"code":"`\"ouchdb:/\"|s4url`"},{"id":"/root/children/208/children/5","type":"text","loc":{"start":63419,"end":63428,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":61,"e":70}},"dim":["","paragraph.208","text.5"],"code":" in each "},{"id":"/root/children/208/children/6","type":"inlineCode","loc":{"start":63428,"end":63438,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":70,"e":80}},"dim":["","paragraph.208","inlineCode.6"],"code":"`.jq.html`"},{"id":"/root/children/208/children/7","type":"text","loc":{"start":63438,"end":63468,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":80,"e":110}},"dim":["","paragraph.208","text.7"],"code":" config block, passed through "},{"id":"/root/children/208/children/8","type":"inlineCode","loc":{"start":63468,"end":63479,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":110,"e":121}},"dim":["","paragraph.208","inlineCode.8"],"code":"`player.js`"},{"id":"/root/children/208/children/9","type":"text","loc":{"start":63479,"end":63501,"line":{"s":1037,"e":1037,"code":["**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:"]},"column":{"s":121,"e":143}},"dim":["","paragraph.208","text.9"],"code":", and instantiated as:"},{"id":"/root/children/209","type":"code","loc":{"start":63503,"end":63580,"line":{"s":1039,"e":1041,"code":["```","STATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")","```"]},"column":{"s":0,"e":3}},"dim":["","code.209"],"code":"```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```","symbName":"code","symbRange":[63582,64803],"symbRangeL":[null,1061],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done\n\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done\n\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps\n\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done\n\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done\n\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use\n\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed\n\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing\n\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.\n\n## Key conventions\n\n- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured\n\n## Voting system\n\n- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.\n\n## Touchbar system API\n\n- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show\n- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.\n\n## Architecture quirks\n\n- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).\n- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).\n\n## Heading-changes / Fragment management\n\n### Files changed\n\n- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`\n- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched\n\n### Dialog rename\n\n`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.\n\n### hasMetaData filter (heading report)\n\n`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.\n\n`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.\n\nDelete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.\n\n### fragmentUrl system\n\nFormat: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).\n\n**Functions:**\n\n- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)\n- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.\n\n### replantRef touchbar action\n\nTriggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:\n\n1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)\n2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`\n\nKey: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.\n\n### URL construction\n\nFollowing `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).\n\n### SQL rows\n\n`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.\n\n## OuchDB (Remote CouchDB/PouchDB)\n\n**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.\n\n**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:\n\n```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```\n\nThe `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).\n\n**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.\n\n**Usage (vegaLinks persistence):**\n\n- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array\n- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`\n- Guard: `if (!STATE.remoteOuchdb) return`\n- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)\n\n**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).\n\n**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.\n\n### CORS conflict: Caddy + PouchDB double headers\n\n`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>\n\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>\n\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>\n\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>\n\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>\n\n<h2>Key conventions</h2>\n\n<ul><li><b>ES modules</b> (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS</li><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>\n\n<h2>Voting system</h2>\n\n<ul><li>SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)</li><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.</li><li><b>Vote helper</b>: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.</li><li><b>Fragment ref helper</b>: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).</li><li><b>JSON options loader</b>: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.</li><li><b>Dialog-campaignes</b>: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.</li><li><b>After voting</b>, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.</li></ul>\n\n<h2>Touchbar system API</h2>\n\n<ul><li>`window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show</li><li>`window.touchbarDispatch(action, element)` — button click dispatch</li><li>`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap</li><li>`data-tooltip` — attribute for click/touch triggered toolbar</li><li>Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)</li><li>`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types</li><li><b>resultAction dispatch</b> (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.</li></ul>\n\n<h2>Architecture quirks</h2>\n\n<ul><li>`window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).</li><li>`proxy.STATE` is the mutable global state object (passed by reference).</li><li>Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.</li><li>Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.</li><li>Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.</li><li>`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.</li><li><b>`echoOut`</b> (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).</li></ul>\n\n<h2>Heading-changes / Fragment management</h2>\n\n<h3>Files changed</h3>\n\n<ul><li>`player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`</li><li>`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter</li><li>`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched</li></ul>\n\n<h3>Dialog rename</h3>\n\n<p>`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.</p>\n\n<h3>hasMetaData filter (heading report)</h3>\n\n<p>`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.</p>\n\n<p>`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.</p>\n\n<p>Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.</p>\n\n<h3>fragmentUrl system</h3>\n\n<p>Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).</p>\n\n<p><b>Functions:</b></p>\n\n<ul><li>`validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)</li><li>`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:<ol><li>Validates fragment URL format</li><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li><li>Returns `{ text, pathInRepo, startLine, endLine }` or `null`</li></ol></li></ul>\n\n<ul><li>`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.</li></ul>\n\n<h3>replantRef touchbar action</h3>\n\n<p>Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:</p>\n\n<ol><li>Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)</li><li>Checks `route.isInEditor` — only works in edit layer</li><li>Validates via `validateFragmentUrl(t)`</li><li>Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`</li><li>Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line</li><li>Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor</li><li>Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`</li></ol>\n\n<p>Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.</p>\n\n<h3>URL construction</h3>\n\n<p>Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).</p>\n\n<h3>SQL rows</h3>\n\n<p>`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.</p>\n\n<h2>OuchDB (Remote CouchDB/PouchDB)</h2>\n\n<p><b>Adapter:</b> `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.</p>\n\n<p><b>Wiring:</b> `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:</p>\n\n<p>```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```</p>\n\n<p>The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).</p>\n\n<p><b>Protocol config:</b> `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.</p>\n\n<p><b>Usage (vegaLinks persistence):</b></p>\n\n<ul><li>`loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array</li><li>`saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`</li><li>Guard: `if (!STATE.remoteOuchdb) return`</li><li>Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)</li></ul>\n\n<p><b>Adapter API:</b> `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).</p>\n\n<p><b>`update()` method:</b> GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.</p>\n\n<h3>CORS conflict: Caddy + PouchDB double headers</h3>\n\n<p>`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:</p>"},{"id":"/root/children/210","type":"paragraph","loc":{"start":63582,"end":63676,"line":{"s":1043,"e":1043,"code":["The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter)."]},"column":{"s":0,"e":94}},"dim":["","paragraph.210"],"code":"The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter)."},{"id":"/root/children/210/children/0","type":"text","loc":{"start":63582,"end":63586,"line":{"s":1043,"e":1043,"code":["The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter)."]},"column":{"s":0,"e":4}},"dim":["","paragraph.210","text.0"],"code":"The "},{"id":"/root/children/210/children/1","type":"inlineCode","loc":{"start":63586,"end":63599,"line":{"s":1043,"e":1043,"code":["The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter)."]},"column":{"s":4,"e":17}},"dim":["","paragraph.210","inlineCode.1"],"code":"`\"/repolero\"`"},{"id":"/root/children/210/children/2","type":"text","loc":{"start":63599,"end":63676,"line":{"s":1043,"e":1043,"code":["The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter)."]},"column":{"s":17,"e":94}},"dim":["","paragraph.210","text.2"],"code":" suffix is the database name, appended at instantiation (not in the adapter)."},{"id":"/root/children/211","type":"paragraph","loc":{"start":63678,"end":63821,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":0,"e":143}},"dim":["","paragraph.211"],"code":"**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."},{"id":"/root/children/211/children/0","type":"strong","loc":{"start":63678,"end":63698,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":0,"e":20}},"dim":["","paragraph.211","strong.0"],"code":"**Protocol config:**"},{"id":"/root/children/211/children/0/children/0","type":"text","loc":{"start":63680,"end":63696,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":2,"e":18}},"dim":["","paragraph.211","strong.0","text.0"],"code":"Protocol config:"},{"id":"/root/children/211/children/1","type":"text","loc":{"start":63698,"end":63699,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":20,"e":21}},"dim":["","paragraph.211","text.1"],"code":" "},{"id":"/root/children/211/children/2","type":"inlineCode","loc":{"start":63699,"end":63715,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":21,"e":37}},"dim":["","paragraph.211","inlineCode.2"],"code":"`s4.config.json`"},{"id":"/root/children/211/children/3","type":"text","loc":{"start":63715,"end":63736,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":37,"e":58}},"dim":["","paragraph.211","text.3"],"code":" protocolRouter maps "},{"id":"/root/children/211/children/4","type":"inlineCode","loc":{"start":63736,"end":63756,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":58,"e":78}},"dim":["","paragraph.211","inlineCode.4"],"code":"`ouchdb:/?(env:loc)`"},{"id":"/root/children/211/children/5","type":"text","loc":{"start":63756,"end":63761,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":78,"e":83}},"dim":["","paragraph.211","text.5"],"code":" and "},{"id":"/root/children/211/children/6","type":"inlineCode","loc":{"start":63761,"end":63784,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":83,"e":106}},"dim":["","paragraph.211","inlineCode.6"],"code":"`ouchdb:/?(env:linode)`"},{"id":"/root/children/211/children/7","type":"text","loc":{"start":63784,"end":63787,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":106,"e":109}},"dim":["","paragraph.211","text.7"],"code":" → "},{"id":"/root/children/211/children/8","type":"inlineCode","loc":{"start":63787,"end":63820,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":109,"e":142}},"dim":["","paragraph.211","inlineCode.8"],"code":"`https://ouchdb.untiloop.website`"},{"id":"/root/children/211/children/9","type":"text","loc":{"start":63820,"end":63821,"line":{"s":1045,"e":1045,"code":["**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`."]},"column":{"s":142,"e":143}},"dim":["","paragraph.211","text.9"],"code":"."},{"id":"/root/children/212","type":"paragraph","loc":{"start":63823,"end":63857,"line":{"s":1047,"e":1047,"code":["**Usage (vegaLinks persistence):**"]},"column":{"s":0,"e":34}},"dim":["","paragraph.212"],"code":"**Usage (vegaLinks persistence):**"},{"id":"/root/children/212/children/0","type":"strong","loc":{"start":63823,"end":63857,"line":{"s":1047,"e":1047,"code":["**Usage (vegaLinks persistence):**"]},"column":{"s":0,"e":34}},"dim":["","paragraph.212","strong.0"],"code":"**Usage (vegaLinks persistence):**"},{"id":"/root/children/212/children/0/children/0","type":"text","loc":{"start":63825,"end":63855,"line":{"s":1047,"e":1047,"code":["**Usage (vegaLinks persistence):**"]},"column":{"s":2,"e":32}},"dim":["","paragraph.212","strong.0","text.0"],"code":"Usage (vegaLinks persistence):"},{"id":"/root/children/213","type":"list","loc":{"start":63859,"end":64164,"line":{"s":1049,"e":1052,"code":["- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array","- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`","- Guard: `if (!STATE.remoteOuchdb) return`","- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"]},"column":{"s":0,"e":81}},"dim":["","list.213"],"code":"- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array\n- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`\n- Guard: `if (!STATE.remoteOuchdb) return`\n- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)","symbName":"list","symbRange":[64166,67200],"symbRangeL":[1049,1102],"outerCode":"- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`\n- Guard: `if (!STATE.remoteOuchdb) return`\n- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)\n\n**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).\n\n**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.\n\n### CORS conflict: Caddy + PouchDB double headers\n\n`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:\n\n```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```\n\nChrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials.\n\n**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:\n\n```json\n{ \"httpd\": { \"enable_cors\": false } }\n```\n\nStart with `--config` flag:\n\n```\nnpx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json\n```\n\n## eisenhover-matrix.js — Vega spec builder\n\n**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`\n\n**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object.\n\n**Config options:**\n\n| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| `width`               | number  | 600     | Spec width                                                                             |\n| `height`              | number  | 500     | Spec height                                                                            |\n| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |\n| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |\n\n**Path utilities:**","outerHtml":"<ul><li>`saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`</li><li>Guard: `if (!STATE.remoteOuchdb) return`</li><li>Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)</li></ul>\n\n<p><b>Adapter API:</b> `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).</p>\n\n<p><b>`update()` method:</b> GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.</p>\n\n<h3>CORS conflict: Caddy + PouchDB double headers</h3>\n\n<p>`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:</p>\n\n<p>```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```</p>\n\n<p>Chrome reads the <b>first</b> `Access-Control-Allow-Origin` (`<i>`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `</i>` with credentials.</p>\n\n<p><b>Fix:</b> Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:</p>\n\n<p>```json\n{ \"httpd\": { \"enable_cors\": false } }\n```</p>\n\n<p>Start with `--config` flag:</p>\n\n<p>```\nnpx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json\n```</p>\n\n<h2>eisenhover-matrix.js — Vega spec builder</h2>\n\n<p><b>Location:</b> `source/repolero-prototype/player/eisenhover-matrix.js`</p>\n\n<p><b>Entrypoint:</b> `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object.</p>\n\n<p><b>Config options:</b></p>\n\n<p>| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| `width`               | number  | 600     | Spec width                                                                             |\n| `height`              | number  | 500     | Spec height                                                                            |\n| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |\n| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |</p>\n\n<p><b>Path utilities:</b></p>"},{"id":"/root/children/213/children/0","type":"listItem","loc":{"start":63859,"end":63948,"line":{"s":1049,"e":1049,"code":["- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"]},"column":{"s":0,"e":89}},"dim":["","list.213","listItem.0"],"code":"- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"},{"id":"/root/children/213/children/0/children/0","type":"paragraph","loc":{"start":63861,"end":63948,"line":{"s":1049,"e":1049,"code":["- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"]},"column":{"s":2,"e":89}},"dim":["","list.213","listItem.0","paragraph.0"],"code":"`loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"},{"id":"/root/children/213/children/0/children/0/children/0","type":"inlineCode","loc":{"start":63861,"end":63878,"line":{"s":1049,"e":1049,"code":["- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"]},"column":{"s":2,"e":19}},"dim":["","list.213","listItem.0","paragraph.0","inlineCode.0"],"code":"`loadVegaLinks()`"},{"id":"/root/children/213/children/0/children/0/children/1","type":"text","loc":{"start":63878,"end":63931,"line":{"s":1049,"e":1049,"code":["- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"]},"column":{"s":19,"e":72}},"dim":["","list.213","listItem.0","paragraph.0","text.1"],"code":" — GETs doc from OuchDB on dashboard init, populates "},{"id":"/root/children/213/children/0/children/0/children/2","type":"inlineCode","loc":{"start":63931,"end":63942,"line":{"s":1049,"e":1049,"code":["- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"]},"column":{"s":72,"e":83}},"dim":["","list.213","listItem.0","paragraph.0","inlineCode.2"],"code":"`vegaLinks`"},{"id":"/root/children/213/children/0/children/0/children/3","type":"text","loc":{"start":63942,"end":63948,"line":{"s":1049,"e":1049,"code":["- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array"]},"column":{"s":83,"e":89}},"dim":["","list.213","listItem.0","paragraph.0","text.3"],"code":" array"},{"id":"/root/children/213/children/1","type":"listItem","loc":{"start":63949,"end":64039,"line":{"s":1050,"e":1050,"code":["- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"]},"column":{"s":0,"e":90}},"dim":["","list.213","listItem.1"],"code":"- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"},{"id":"/root/children/213/children/1/children/0","type":"paragraph","loc":{"start":63951,"end":64039,"line":{"s":1050,"e":1050,"code":["- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"]},"column":{"s":2,"e":90}},"dim":["","list.213","listItem.1","paragraph.0"],"code":"`saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"},{"id":"/root/children/213/children/1/children/0/children/0","type":"inlineCode","loc":{"start":63951,"end":63968,"line":{"s":1050,"e":1050,"code":["- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"]},"column":{"s":2,"e":19}},"dim":["","list.213","listItem.1","paragraph.0","inlineCode.0"],"code":"`saveVegaLinks()`"},{"id":"/root/children/213/children/1/children/0/children/1","type":"text","loc":{"start":63968,"end":63977,"line":{"s":1050,"e":1050,"code":["- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"]},"column":{"s":19,"e":28}},"dim":["","list.213","listItem.1","paragraph.0","text.1"],"code":" — calls "},{"id":"/root/children/213/children/1/children/0/children/2","type":"inlineCode","loc":{"start":63977,"end":64006,"line":{"s":1050,"e":1050,"code":["- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"]},"column":{"s":28,"e":57}},"dim":["","list.213","listItem.1","paragraph.0","inlineCode.2"],"code":"`STATE.remoteOuchdb.update()`"},{"id":"/root/children/213/children/1/children/0/children/3","type":"text","loc":{"start":64006,"end":64028,"line":{"s":1050,"e":1050,"code":["- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"]},"column":{"s":57,"e":79}},"dim":["","list.213","listItem.1","paragraph.0","text.3"],"code":" after push/splice on "},{"id":"/root/children/213/children/1/children/0/children/4","type":"inlineCode","loc":{"start":64028,"end":64039,"line":{"s":1050,"e":1050,"code":["- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`"]},"column":{"s":79,"e":90}},"dim":["","list.213","listItem.1","paragraph.0","inlineCode.4"],"code":"`vegaLinks`"},{"id":"/root/children/213/children/2","type":"listItem","loc":{"start":64040,"end":64082,"line":{"s":1051,"e":1051,"code":["- Guard: `if (!STATE.remoteOuchdb) return`"]},"column":{"s":0,"e":42}},"dim":["","list.213","listItem.2"],"code":"- Guard: `if (!STATE.remoteOuchdb) return`"},{"id":"/root/children/213/children/2/children/0","type":"paragraph","loc":{"start":64042,"end":64082,"line":{"s":1051,"e":1051,"code":["- Guard: `if (!STATE.remoteOuchdb) return`"]},"column":{"s":2,"e":42}},"dim":["","list.213","listItem.2","paragraph.0"],"code":"Guard: `if (!STATE.remoteOuchdb) return`"},{"id":"/root/children/213/children/2/children/0/children/0","type":"text","loc":{"start":64042,"end":64049,"line":{"s":1051,"e":1051,"code":["- Guard: `if (!STATE.remoteOuchdb) return`"]},"column":{"s":2,"e":9}},"dim":["","list.213","listItem.2","paragraph.0","text.0"],"code":"Guard: "},{"id":"/root/children/213/children/2/children/0/children/1","type":"inlineCode","loc":{"start":64049,"end":64082,"line":{"s":1051,"e":1051,"code":["- Guard: `if (!STATE.remoteOuchdb) return`"]},"column":{"s":9,"e":42}},"dim":["","list.213","listItem.2","paragraph.0","inlineCode.1"],"code":"`if (!STATE.remoteOuchdb) return`"},{"id":"/root/children/213/children/3","type":"listItem","loc":{"start":64083,"end":64164,"line":{"s":1052,"e":1052,"code":["- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"]},"column":{"s":0,"e":81}},"dim":["","list.213","listItem.3"],"code":"- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"},{"id":"/root/children/213/children/3/children/0","type":"paragraph","loc":{"start":64085,"end":64164,"line":{"s":1052,"e":1052,"code":["- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"]},"column":{"s":2,"e":81}},"dim":["","list.213","listItem.3","paragraph.0"],"code":"Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"},{"id":"/root/children/213/children/3/children/0/children/0","type":"text","loc":{"start":64085,"end":64101,"line":{"s":1052,"e":1052,"code":["- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"]},"column":{"s":2,"e":18}},"dim":["","list.213","listItem.3","paragraph.0","text.0"],"code":"Doc ID pattern: "},{"id":"/root/children/213/children/3/children/0/children/1","type":"inlineCode","loc":{"start":64101,"end":64135,"line":{"s":1052,"e":1052,"code":["- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"]},"column":{"s":18,"e":52}},"dim":["","list.213","listItem.3","paragraph.0","inlineCode.1"],"code":"`{repoName}/campaign/{campaignId}`"},{"id":"/root/children/213/children/3/children/0/children/2","type":"text","loc":{"start":64135,"end":64164,"line":{"s":1052,"e":1052,"code":["- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)"]},"column":{"s":52,"e":81}},"dim":["","list.213","listItem.3","paragraph.0","text.2"],"code":" (CouchDB slash-separated ID)"},{"id":"/root/children/214","type":"paragraph","loc":{"start":64166,"end":64333,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":0,"e":167}},"dim":["","paragraph.214"],"code":"**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."},{"id":"/root/children/214/children/0","type":"strong","loc":{"start":64166,"end":64182,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":0,"e":16}},"dim":["","paragraph.214","strong.0"],"code":"**Adapter API:**"},{"id":"/root/children/214/children/0/children/0","type":"text","loc":{"start":64168,"end":64180,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":2,"e":14}},"dim":["","paragraph.214","strong.0","text.0"],"code":"Adapter API:"},{"id":"/root/children/214/children/1","type":"text","loc":{"start":64182,"end":64183,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":16,"e":17}},"dim":["","paragraph.214","text.1"],"code":" "},{"id":"/root/children/214/children/2","type":"inlineCode","loc":{"start":64183,"end":64195,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":17,"e":29}},"dim":["","paragraph.214","inlineCode.2"],"code":"`get(docId)`"},{"id":"/root/children/214/children/3","type":"text","loc":{"start":64195,"end":64197,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":29,"e":31}},"dim":["","paragraph.214","text.3"],"code":", "},{"id":"/root/children/214/children/4","type":"inlineCode","loc":{"start":64197,"end":64219,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":31,"e":53}},"dim":["","paragraph.214","inlineCode.4"],"code":"`put(docId, document)`"},{"id":"/root/children/214/children/5","type":"text","loc":{"start":64219,"end":64221,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":53,"e":55}},"dim":["","paragraph.214","text.5"],"code":", "},{"id":"/root/children/214/children/6","type":"inlineCode","loc":{"start":64221,"end":64241,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":55,"e":75}},"dim":["","paragraph.214","inlineCode.6"],"code":"`delete(docId, rev)`"},{"id":"/root/children/214/children/7","type":"text","loc":{"start":64241,"end":64243,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":75,"e":77}},"dim":["","paragraph.214","text.7"],"code":", "},{"id":"/root/children/214/children/8","type":"inlineCode","loc":{"start":64243,"end":64261,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":77,"e":95}},"dim":["","paragraph.214","inlineCode.8"],"code":"`allDocs(options)`"},{"id":"/root/children/214/children/9","type":"text","loc":{"start":64261,"end":64263,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":95,"e":97}},"dim":["","paragraph.214","text.9"],"code":", "},{"id":"/root/children/214/children/10","type":"inlineCode","loc":{"start":64263,"end":64288,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":97,"e":122}},"dim":["","paragraph.214","inlineCode.10"],"code":"`update(docId, document)`"},{"id":"/root/children/214/children/11","type":"text","loc":{"start":64288,"end":64297,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":122,"e":131}},"dim":["","paragraph.214","text.11"],"code":" — reads "},{"id":"/root/children/214/children/12","type":"inlineCode","loc":{"start":64297,"end":64303,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":131,"e":137}},"dim":["","paragraph.214","inlineCode.12"],"code":"`_rev`"},{"id":"/root/children/214/children/13","type":"text","loc":{"start":64303,"end":64333,"line":{"s":1054,"e":1054,"code":["**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update)."]},"column":{"s":137,"e":167}},"dim":["","paragraph.214","text.13"],"code":" then PUTs (create or update)."},{"id":"/root/children/215","type":"paragraph","loc":{"start":64335,"end":64526,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":0,"e":191}},"dim":["","paragraph.215"],"code":"**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."},{"id":"/root/children/215/children/0","type":"strong","loc":{"start":64335,"end":64357,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":0,"e":22}},"dim":["","paragraph.215","strong.0"],"code":"**`update()` method:**"},{"id":"/root/children/215/children/0/children/0","type":"inlineCode","loc":{"start":64337,"end":64347,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":2,"e":12}},"dim":["","paragraph.215","strong.0","inlineCode.0"],"code":"`update()`"},{"id":"/root/children/215/children/0/children/1","type":"text","loc":{"start":64347,"end":64355,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":12,"e":20}},"dim":["","paragraph.215","strong.0","text.1"],"code":" method:"},{"id":"/root/children/215/children/1","type":"text","loc":{"start":64357,"end":64380,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":22,"e":45}},"dim":["","paragraph.215","text.1"],"code":" GETs existing doc for "},{"id":"/root/children/215/children/2","type":"inlineCode","loc":{"start":64380,"end":64386,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":45,"e":51}},"dim":["","paragraph.215","inlineCode.2"],"code":"`_rev`"},{"id":"/root/children/215/children/3","type":"text","loc":{"start":64386,"end":64443,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":51,"e":108}},"dim":["","paragraph.215","text.3"],"code":", catches 404 silently for fresh create, then PUTs. Uses "},{"id":"/root/children/215/children/4","type":"inlineCode","loc":{"start":64443,"end":64459,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":108,"e":124}},"dim":["","paragraph.215","inlineCode.4"],"code":"`this.request()`"},{"id":"/root/children/215/children/5","type":"text","loc":{"start":64459,"end":64465,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":124,"e":130}},"dim":["","paragraph.215","text.5"],"code":" (not "},{"id":"/root/children/215/children/6","type":"inlineCode","loc":{"start":64465,"end":64477,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":130,"e":142}},"dim":["","paragraph.215","inlineCode.6"],"code":"`this.get()`"},{"id":"/root/children/215/children/7","type":"text","loc":{"start":64477,"end":64494,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":142,"e":159}},"dim":["","paragraph.215","text.7"],"code":") to avoid noisy "},{"id":"/root/children/215/children/8","type":"inlineCode","loc":{"start":64494,"end":64509,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":159,"e":174}},"dim":["","paragraph.215","inlineCode.8"],"code":"`console.error`"},{"id":"/root/children/215/children/9","type":"text","loc":{"start":64509,"end":64526,"line":{"s":1056,"e":1056,"code":["**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404."]},"column":{"s":174,"e":191}},"dim":["","paragraph.215","text.9"],"code":" on expected 404."},{"id":"/root/children/216","type":"heading","loc":{"start":64528,"end":64577,"line":{"s":1058,"e":1058,"code":["### CORS conflict: Caddy + PouchDB double headers"]},"column":{"s":0,"e":49}},"dim":["","heading.216"],"code":"### CORS conflict: Caddy + PouchDB double headers","symbName":"heading","symbRange":[64579,65596],"symbRangeL":[1058,1081],"outerCode":"\n`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:\n\n```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```\n\nChrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials.\n\n**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:\n\n```json\n{ \"httpd\": { \"enable_cors\": false } }\n```\n\nStart with `--config` flag:\n\n```\nnpx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json\n```","outerHtml":"\n<p>`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:</p>\n\n<p>```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```</p>\n\n<p>Chrome reads the <b>first</b> `Access-Control-Allow-Origin` (`<i>`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `</i>` with credentials.</p>\n\n<p><b>Fix:</b> Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:</p>\n\n<p>```json\n{ \"httpd\": { \"enable_cors\": false } }\n```</p>\n\n<p>Start with `--config` flag:</p>\n\n<p>```\nnpx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json\n```</p>"},{"id":"/root/children/216/children/0","type":"text","loc":{"start":64532,"end":64577,"line":{"s":1058,"e":1058,"code":["### CORS conflict: Caddy + PouchDB double headers"]},"column":{"s":4,"e":49}},"dim":["","heading.216","text.0"],"code":"CORS conflict: Caddy + PouchDB double headers"},{"id":"/root/children/217","type":"paragraph","loc":{"start":64579,"end":64803,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":0,"e":224}},"dim":["","paragraph.217"],"code":"`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"},{"id":"/root/children/217/children/0","type":"inlineCode","loc":{"start":64579,"end":64595,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":0,"e":16}},"dim":["","paragraph.217","inlineCode.0"],"code":"`pouchdb-server`"},{"id":"/root/children/217/children/1","type":"text","loc":{"start":64595,"end":64625,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":16,"e":46}},"dim":["","paragraph.217","text.1"],"code":" has CORS enabled by default ("},{"id":"/root/children/217/children/2","type":"inlineCode","loc":{"start":64625,"end":64650,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":46,"e":71}},"dim":["","paragraph.217","inlineCode.2"],"code":"`httpd.enable_cors: true`"},{"id":"/root/children/217/children/3","type":"text","loc":{"start":64650,"end":64652,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":71,"e":73}},"dim":["","paragraph.217","text.3"],"code":", "},{"id":"/root/children/217/children/4","type":"inlineCode","loc":{"start":64652,"end":64676,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":73,"e":97}},"dim":["","paragraph.217","inlineCode.4"],"code":"`cors.credentials: true`"},{"id":"/root/children/217/children/5","type":"text","loc":{"start":64676,"end":64680,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":97,"e":101}},"dim":["","paragraph.217","text.5"],"code":" in "},{"id":"/root/children/217/children/6","type":"inlineCode","loc":{"start":64680,"end":64693,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":101,"e":114}},"dim":["","paragraph.217","inlineCode.6"],"code":"`lib/cors.js`"},{"id":"/root/children/217/children/7","type":"text","loc":{"start":64693,"end":64803,"line":{"s":1060,"e":1060,"code":["`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"]},"column":{"s":114,"e":224}},"dim":["","paragraph.217","text.7"],"code":"). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:"},{"id":"/root/children/218","type":"code","loc":{"start":64805,"end":65097,"line":{"s":1062,"e":1066,"code":["```","Access-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)","Access-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)","Access-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)","```"]},"column":{"s":0,"e":3}},"dim":["","code.218"],"code":"```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```","symbName":"code","symbRange":[65099,65410],"symbRangeL":[null,1071],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done\n\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done\n\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps\n\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done\n\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done\n\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use\n\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed\n\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing\n\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.\n\n## Key conventions\n\n- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured\n\n## Voting system\n\n- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.\n\n## Touchbar system API\n\n- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show\n- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.\n\n## Architecture quirks\n\n- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).\n- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).\n\n## Heading-changes / Fragment management\n\n### Files changed\n\n- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`\n- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched\n\n### Dialog rename\n\n`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.\n\n### hasMetaData filter (heading report)\n\n`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.\n\n`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.\n\nDelete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.\n\n### fragmentUrl system\n\nFormat: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).\n\n**Functions:**\n\n- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)\n- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.\n\n### replantRef touchbar action\n\nTriggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:\n\n1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)\n2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`\n\nKey: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.\n\n### URL construction\n\nFollowing `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).\n\n### SQL rows\n\n`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.\n\n## OuchDB (Remote CouchDB/PouchDB)\n\n**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.\n\n**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:\n\n```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```\n\nThe `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).\n\n**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.\n\n**Usage (vegaLinks persistence):**\n\n- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array\n- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`\n- Guard: `if (!STATE.remoteOuchdb) return`\n- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)\n\n**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).\n\n**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.\n\n### CORS conflict: Caddy + PouchDB double headers\n\n`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:\n\n```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```\n\nChrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials.\n\n**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>\n\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>\n\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>\n\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>\n\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>\n\n<h2>Key conventions</h2>\n\n<ul><li><b>ES modules</b> (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS</li><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>\n\n<h2>Voting system</h2>\n\n<ul><li>SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)</li><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.</li><li><b>Vote helper</b>: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.</li><li><b>Fragment ref helper</b>: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).</li><li><b>JSON options loader</b>: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.</li><li><b>Dialog-campaignes</b>: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.</li><li><b>After voting</b>, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.</li></ul>\n\n<h2>Touchbar system API</h2>\n\n<ul><li>`window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show</li><li>`window.touchbarDispatch(action, element)` — button click dispatch</li><li>`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap</li><li>`data-tooltip` — attribute for click/touch triggered toolbar</li><li>Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)</li><li>`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types</li><li><b>resultAction dispatch</b> (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.</li></ul>\n\n<h2>Architecture quirks</h2>\n\n<ul><li>`window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).</li><li>`proxy.STATE` is the mutable global state object (passed by reference).</li><li>Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.</li><li>Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.</li><li>Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.</li><li>`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.</li><li><b>`echoOut`</b> (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).</li></ul>\n\n<h2>Heading-changes / Fragment management</h2>\n\n<h3>Files changed</h3>\n\n<ul><li>`player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`</li><li>`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter</li><li>`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched</li></ul>\n\n<h3>Dialog rename</h3>\n\n<p>`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.</p>\n\n<h3>hasMetaData filter (heading report)</h3>\n\n<p>`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.</p>\n\n<p>`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.</p>\n\n<p>Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.</p>\n\n<h3>fragmentUrl system</h3>\n\n<p>Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).</p>\n\n<p><b>Functions:</b></p>\n\n<ul><li>`validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)</li><li>`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:<ol><li>Validates fragment URL format</li><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li><li>Returns `{ text, pathInRepo, startLine, endLine }` or `null`</li></ol></li></ul>\n\n<ul><li>`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.</li></ul>\n\n<h3>replantRef touchbar action</h3>\n\n<p>Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:</p>\n\n<ol><li>Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)</li><li>Checks `route.isInEditor` — only works in edit layer</li><li>Validates via `validateFragmentUrl(t)`</li><li>Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`</li><li>Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line</li><li>Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor</li><li>Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`</li></ol>\n\n<p>Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.</p>\n\n<h3>URL construction</h3>\n\n<p>Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).</p>\n\n<h3>SQL rows</h3>\n\n<p>`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.</p>\n\n<h2>OuchDB (Remote CouchDB/PouchDB)</h2>\n\n<p><b>Adapter:</b> `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.</p>\n\n<p><b>Wiring:</b> `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:</p>\n\n<p>```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```</p>\n\n<p>The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).</p>\n\n<p><b>Protocol config:</b> `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.</p>\n\n<p><b>Usage (vegaLinks persistence):</b></p>\n\n<ul><li>`loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array</li><li>`saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`</li><li>Guard: `if (!STATE.remoteOuchdb) return`</li><li>Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)</li></ul>\n\n<p><b>Adapter API:</b> `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).</p>\n\n<p><b>`update()` method:</b> GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.</p>\n\n<h3>CORS conflict: Caddy + PouchDB double headers</h3>\n\n<p>`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:</p>\n\n<p>```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```</p>\n\n<p>Chrome reads the <b>first</b> `Access-Control-Allow-Origin` (`<i>`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `</i>` with credentials.</p>\n\n<p><b>Fix:</b> Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:</p>"},{"id":"/root/children/219","type":"paragraph","loc":{"start":65099,"end":65268,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":0,"e":169}},"dim":["","paragraph.219"],"code":"Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."},{"id":"/root/children/219/children/0","type":"text","loc":{"start":65099,"end":65116,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":0,"e":17}},"dim":["","paragraph.219","text.0"],"code":"Chrome reads the "},{"id":"/root/children/219/children/1","type":"strong","loc":{"start":65116,"end":65125,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":17,"e":26}},"dim":["","paragraph.219","strong.1"],"code":"**first**"},{"id":"/root/children/219/children/1/children/0","type":"text","loc":{"start":65118,"end":65123,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":19,"e":24}},"dim":["","paragraph.219","strong.1","text.0"],"code":"first"},{"id":"/root/children/219/children/2","type":"text","loc":{"start":65125,"end":65126,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":26,"e":27}},"dim":["","paragraph.219","text.2"],"code":" "},{"id":"/root/children/219/children/3","type":"inlineCode","loc":{"start":65126,"end":65155,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":27,"e":56}},"dim":["","paragraph.219","inlineCode.3"],"code":"`Access-Control-Allow-Origin`"},{"id":"/root/children/219/children/4","type":"text","loc":{"start":65155,"end":65157,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":56,"e":58}},"dim":["","paragraph.219","text.4"],"code":" ("},{"id":"/root/children/219/children/5","type":"inlineCode","loc":{"start":65157,"end":65160,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":58,"e":61}},"dim":["","paragraph.219","inlineCode.5"],"code":"`*`"},{"id":"/root/children/219/children/6","type":"text","loc":{"start":65160,"end":65168,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":61,"e":69}},"dim":["","paragraph.219","text.6"],"code":"), sees "},{"id":"/root/children/219/children/7","type":"inlineCode","loc":{"start":65168,"end":65208,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":69,"e":109}},"dim":["","paragraph.219","inlineCode.7"],"code":"`Access-Control-Allow-Credentials: true`"},{"id":"/root/children/219/children/8","type":"text","loc":{"start":65208,"end":65247,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":109,"e":148}},"dim":["","paragraph.219","text.8"],"code":", and rejects — the Fetch spec forbids "},{"id":"/root/children/219/children/9","type":"inlineCode","loc":{"start":65247,"end":65250,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":148,"e":151}},"dim":["","paragraph.219","inlineCode.9"],"code":"`*`"},{"id":"/root/children/219/children/10","type":"text","loc":{"start":65250,"end":65268,"line":{"s":1068,"e":1068,"code":["Chrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials."]},"column":{"s":151,"e":169}},"dim":["","paragraph.219","text.10"],"code":" with credentials."},{"id":"/root/children/220","type":"paragraph","loc":{"start":65270,"end":65410,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":0,"e":140}},"dim":["","paragraph.220"],"code":"**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"},{"id":"/root/children/220/children/0","type":"strong","loc":{"start":65270,"end":65278,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":0,"e":8}},"dim":["","paragraph.220","strong.0"],"code":"**Fix:**"},{"id":"/root/children/220/children/0/children/0","type":"text","loc":{"start":65272,"end":65276,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":2,"e":6}},"dim":["","paragraph.220","strong.0","text.0"],"code":"Fix:"},{"id":"/root/children/220/children/1","type":"text","loc":{"start":65278,"end":65295,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":8,"e":25}},"dim":["","paragraph.220","text.1"],"code":" Disable CORS in "},{"id":"/root/children/220/children/2","type":"inlineCode","loc":{"start":65295,"end":65311,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":25,"e":41}},"dim":["","paragraph.220","inlineCode.2"],"code":"`pouchdb-server`"},{"id":"/root/children/220/children/3","type":"text","loc":{"start":65311,"end":65359,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":41,"e":89}},"dim":["","paragraph.220","text.3"],"code":" since Caddy already handles it. Config file at "},{"id":"/root/children/220/children/4","type":"inlineCode","loc":{"start":65359,"end":65409,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":89,"e":139}},"dim":["","paragraph.220","inlineCode.4"],"code":"`linode-server/caddy/linode/pouchdb-cors-off.json`"},{"id":"/root/children/220/children/5","type":"text","loc":{"start":65409,"end":65410,"line":{"s":1070,"e":1070,"code":["**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:"]},"column":{"s":139,"e":140}},"dim":["","paragraph.220","text.5"],"code":":"},{"id":"/root/children/221","type":"code","loc":{"start":65412,"end":65461,"line":{"s":1072,"e":1074,"code":["```json","{ \"httpd\": { \"enable_cors\": false } }","```"]},"column":{"s":0,"e":3}},"dim":["","code.221"],"code":"```json\n{ \"httpd\": { \"enable_cors\": false } }\n```","symbName":"code","symbRange":[65463,65490],"symbRangeL":[null,1077],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done\n\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done\n\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps\n\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done\n\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done\n\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use\n\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed\n\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing\n\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.\n\n## Key conventions\n\n- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured\n\n## Voting system\n\n- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.\n\n## Touchbar system API\n\n- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show\n- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.\n\n## Architecture quirks\n\n- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).\n- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).\n\n## Heading-changes / Fragment management\n\n### Files changed\n\n- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`\n- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched\n\n### Dialog rename\n\n`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.\n\n### hasMetaData filter (heading report)\n\n`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.\n\n`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.\n\nDelete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.\n\n### fragmentUrl system\n\nFormat: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).\n\n**Functions:**\n\n- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)\n- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.\n\n### replantRef touchbar action\n\nTriggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:\n\n1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)\n2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`\n\nKey: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.\n\n### URL construction\n\nFollowing `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).\n\n### SQL rows\n\n`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.\n\n## OuchDB (Remote CouchDB/PouchDB)\n\n**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.\n\n**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:\n\n```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```\n\nThe `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).\n\n**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.\n\n**Usage (vegaLinks persistence):**\n\n- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array\n- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`\n- Guard: `if (!STATE.remoteOuchdb) return`\n- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)\n\n**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).\n\n**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.\n\n### CORS conflict: Caddy + PouchDB double headers\n\n`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:\n\n```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```\n\nChrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials.\n\n**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:\n\n```json\n{ \"httpd\": { \"enable_cors\": false } }\n```\n\nStart with `--config` flag:","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>\n\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>\n\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>\n\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>\n\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>\n\n<h2>Key conventions</h2>\n\n<ul><li><b>ES modules</b> (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS</li><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>\n\n<h2>Voting system</h2>\n\n<ul><li>SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)</li><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.</li><li><b>Vote helper</b>: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.</li><li><b>Fragment ref helper</b>: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).</li><li><b>JSON options loader</b>: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.</li><li><b>Dialog-campaignes</b>: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.</li><li><b>After voting</b>, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.</li></ul>\n\n<h2>Touchbar system API</h2>\n\n<ul><li>`window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show</li><li>`window.touchbarDispatch(action, element)` — button click dispatch</li><li>`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap</li><li>`data-tooltip` — attribute for click/touch triggered toolbar</li><li>Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)</li><li>`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types</li><li><b>resultAction dispatch</b> (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.</li></ul>\n\n<h2>Architecture quirks</h2>\n\n<ul><li>`window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).</li><li>`proxy.STATE` is the mutable global state object (passed by reference).</li><li>Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.</li><li>Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.</li><li>Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.</li><li>`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.</li><li><b>`echoOut`</b> (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).</li></ul>\n\n<h2>Heading-changes / Fragment management</h2>\n\n<h3>Files changed</h3>\n\n<ul><li>`player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`</li><li>`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter</li><li>`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched</li></ul>\n\n<h3>Dialog rename</h3>\n\n<p>`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.</p>\n\n<h3>hasMetaData filter (heading report)</h3>\n\n<p>`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.</p>\n\n<p>`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.</p>\n\n<p>Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.</p>\n\n<h3>fragmentUrl system</h3>\n\n<p>Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).</p>\n\n<p><b>Functions:</b></p>\n\n<ul><li>`validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)</li><li>`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:<ol><li>Validates fragment URL format</li><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li><li>Returns `{ text, pathInRepo, startLine, endLine }` or `null`</li></ol></li></ul>\n\n<ul><li>`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.</li></ul>\n\n<h3>replantRef touchbar action</h3>\n\n<p>Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:</p>\n\n<ol><li>Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)</li><li>Checks `route.isInEditor` — only works in edit layer</li><li>Validates via `validateFragmentUrl(t)`</li><li>Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`</li><li>Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line</li><li>Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor</li><li>Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`</li></ol>\n\n<p>Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.</p>\n\n<h3>URL construction</h3>\n\n<p>Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).</p>\n\n<h3>SQL rows</h3>\n\n<p>`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.</p>\n\n<h2>OuchDB (Remote CouchDB/PouchDB)</h2>\n\n<p><b>Adapter:</b> `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.</p>\n\n<p><b>Wiring:</b> `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:</p>\n\n<p>```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```</p>\n\n<p>The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).</p>\n\n<p><b>Protocol config:</b> `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.</p>\n\n<p><b>Usage (vegaLinks persistence):</b></p>\n\n<ul><li>`loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array</li><li>`saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`</li><li>Guard: `if (!STATE.remoteOuchdb) return`</li><li>Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)</li></ul>\n\n<p><b>Adapter API:</b> `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).</p>\n\n<p><b>`update()` method:</b> GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.</p>\n\n<h3>CORS conflict: Caddy + PouchDB double headers</h3>\n\n<p>`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:</p>\n\n<p>```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```</p>\n\n<p>Chrome reads the <b>first</b> `Access-Control-Allow-Origin` (`<i>`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `</i>` with credentials.</p>\n\n<p><b>Fix:</b> Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:</p>\n\n<p>```json\n{ \"httpd\": { \"enable_cors\": false } }\n```</p>\n\n<p>Start with `--config` flag:</p>"},{"id":"/root/children/222","type":"paragraph","loc":{"start":65463,"end":65490,"line":{"s":1076,"e":1076,"code":["Start with `--config` flag:"]},"column":{"s":0,"e":27}},"dim":["","paragraph.222"],"code":"Start with `--config` flag:"},{"id":"/root/children/222/children/0","type":"text","loc":{"start":65463,"end":65474,"line":{"s":1076,"e":1076,"code":["Start with `--config` flag:"]},"column":{"s":0,"e":11}},"dim":["","paragraph.222","text.0"],"code":"Start with "},{"id":"/root/children/222/children/1","type":"inlineCode","loc":{"start":65474,"end":65484,"line":{"s":1076,"e":1076,"code":["Start with `--config` flag:"]},"column":{"s":11,"e":21}},"dim":["","paragraph.222","inlineCode.1"],"code":"`--config`"},{"id":"/root/children/222/children/2","type":"text","loc":{"start":65484,"end":65490,"line":{"s":1076,"e":1076,"code":["Start with `--config` flag:"]},"column":{"s":21,"e":27}},"dim":["","paragraph.222","text.2"],"code":" flag:"},{"id":"/root/children/223","type":"code","loc":{"start":65492,"end":65596,"line":{"s":1078,"e":1080,"code":["```","npx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json","```"]},"column":{"s":0,"e":3}},"dim":["","code.223"],"code":"```\nnpx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json\n```","symbName":"code","symbRange":[65598,70554],"symbRangeL":[null,1135],"outerCode":";{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }\n\n# player.js - Analysis & Summary\n\n## Overview\n\n`player.js` is the main orchestrator of the **repolero-prototype** app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (**glass**, **paper**, **space**, **edit**), and real-time collaboration via STOMP/RabbitMQ.\n\nThe HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.\n\n## repolero-prototype — UI concept (from repolero-prototype.mdd)\n\n### Design principles\n\n- **Minimal layout** (water.css-inspired)\n- **Mouse-less interaction** — keyboard-first\n- **Keyboard shortcuts follow key ergonomics, not semantics** — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE\n\n### Three layers\n\n| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| **space**  | Map view                            | player/map-show.js (OpenLayers)                |\n| **paper**  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| **editor** | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| **glass**  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |\n\n### Glass menu selector\n\nUses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete\n\n### Visualization\n\n- **Vega-Lite**: parallel coords, word cloud, radar, radial tree, calendar, heatmap\n- **D3**: hierarchy pack, message sequence chart (rc page)\n\n### Data dynamics\n\n- Webhooks: repo-sync, map-show tile generation\n- Portlets concept (TODO)\n- Singularity/plurarity: repolero subsystem of fragments and stages (TODO)\n\n### UI tricks\n\n- tippy.js for tooltips\n- icomoon icons as font\n\n## ssss — super simple static site generator\n\nrepolero-prototype is built with **ssss**, a custom static site generator.\n\n### Principles\n\n- **Petri-net flow**: many sources flow into one output, many outputs flow into one deployment\n- **JSON inheritance (meld)**: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`\n\n### MPA concept\n\nMulti Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.\n\n### Folder structure\n\n```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  *                         — adjacent to ssss/*\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  *.html                    ← source/*.jq.json + *.jq.html rendered\n  data/**                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```\n\n### Tech choices (from ssss.mdd)\n\n| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |\n\n### Usage\n\n`yarn serve`, `yarn watch`, `yoffee`\n\n## Build Pipeline\n\nEach `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):\n\n```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html\n\nsource/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html\n\nsource/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```\n\n**All jq.html pages:**\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)\n\n`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the **alternative player shell** introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.\n\n**The .jq.json structure** (shared across pages):\n\n```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```\n\nThe `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.\n\nThe jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.\n\n**What gets transformed:**\n\n- HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`\n- Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`\n- Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements\n- Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`\n\n**What passes through untouched:**\n\n- `<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)\n- Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`\n- Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`\n\n## jq-html Templating\n\n`view.jq.html` uses a custom templating engine called **jq-html**. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.\n\n**Three modes:**\n\n| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |\n\nThe iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.\n\n**Common helpers used in the template:**\n\n| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |\n\n**How the template feeds player.js** (in the boot `<script>`):\n\n```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```\n\nServer-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.\n\n## How It Works (HTML + JS together)\n\n### The DOM Skeleton (view.jq.html)\n\nThe HTML defines five top-level containers, only one visible at a time:\n\n```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```\n\nLayers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.\n\n### Boot Sequence\n\n```\nview.jq.html <script type=\"module\">\n  1. jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)\n  2. initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh\n  3. ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout\n  4. player({ssss, ...all config...})        -> boots the app\n```\n\n### Inside player()\n\n```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```\n\n### renderLayerMode - the core switch\n\nThis is the heart. It receives a `targetLayer` and toggles the DOM:\n\n```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```\n\n### How the HTML sections map to player.js functions\n\n| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |\n\n### Data Flow: Search\n\n```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```\n\n### Data Flow: MQ (real-time sync)\n\n```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})\n\noutgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```\n\n### Persistence Model\n\nAll state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:\n\n| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |\n\n## ssss.js - The Utility Library\n\n`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.\n\n### What ssss provides to player.js\n\n| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| **Boot**            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| **Fetch**           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| **Protocol URLs**   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| **MQ**              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| **Rendering**       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| **Webhooks**        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| **DOM**             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| **Navigation**      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| **Misc**            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| **String**          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| **Parsing**         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| **Components**      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| **Custom Elements** | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |\n\n### ssss Sub-modules\n\n```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```\n\n### How ssss.bootS4() drives the app\n\n```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)\n  1. Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)\n  2. Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)\n  3. Wait for document.readyState === 'complete'\n  4. Register page routes (if any)\n  5. Call fnBootStart() -> player's boot callback\n  6. Router.init(null, routeFallback)  -> hash change handler\n  7. Router.navigate(defaultRoute)     -> first render\n```\n\nThe `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.\n\n### Key design notes\n\n- **ETag caching**: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.\n- **Protocol router**: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.\n- **Tracked event listeners**: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.\n- **window.s4util**: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.\n\n## Architecture\n\n```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```\n\n## Layers (routing)\n\n| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |\n\n## File Summaries\n\n| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.\\* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |\n\n## Layout Engines (Eisenhower Matrix)\n\n| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| **normal** (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| **boss** (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| **boss2** (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| **boss3** (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| **boss4** (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| **boss5** (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |\n\nAll active layout engines (boss2–5, normal) set two width fields per task:\n\n- `taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only\n- `width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel\n\n## Found TASK: Comments\n\n| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |\n\nNote: The autoZoom TASK in map-show.js has been resolved.\n\n## Theme structure\n\nTwo LESS files serve the same HTML with different skins:\n\n- `theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)\n- `theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.\n\n**Key structural difference resolved:** `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.\n\n### Dialog backdrop blur\n\n`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).\n\n## Observations\n\n1. **player.js is large** (~1022 lines). `renderLayerMode` handles all layer switching in one switch.\n2. **PROXY/proxy pattern** — see detailed analysis below.\n3. **Commented-out code** - present in player.js and ui-touchbar.js.\n4. **search/filter-maker.js** appears to be an older duplicate of filter-base.js logic.\n5. **search/search-space.js** is empty.\n6. **New file**: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.\n7. **New file**: `player/page-file-manager.js` - file manager actions module.\n8. **New file**: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).\n9. **Global window assignments** - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. **One TASK found** in rc.jq.html (empty execState function)\n11. **#e-main / #e-secondary** in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. **Glass is a toggle overlay**, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.\n\n---\n\n## Analysis: PROXY vs proxy\n\n### Intended pattern\n\n- `PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag\n- `proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`\n\n### What actually happens\n\nThe builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.\n\n**Merge point** (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.\n\n**Call site** (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.\n\n### Violations — proxy used as reader\n\n| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |\n\n### Consequence\n\n`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.\n\n### Fix applied\n\nEnforced the pattern: `PROXY` is the singleton, functions read from it directly.\n\n**Changes in player.js:**\n\n- Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site\n- Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`\n- All internal functions now read from module-level `PROXY`\n- Removed unused `PROXY` shadow param from `savePaper()`\n- Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)\n- `addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`\n\n**Changes in external modules:**\n\n- `larry.js`: removed unused `PROXY` param from `detectActor()`\n- `search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`\n- `glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call\n- `glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const\n\n---\n\n## Refactoring: boot() + actions.js\n\n### What changed\n\nEliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.\n\n### New files\n\n**actions.js** — shared `window.*` function registrations:\n\n```\n\nregisterUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook\n\nregisterRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)\n\n```\n\n### boot() in player.js\n\n```\n\nexport async function boot(ssss, opts = {})\n\n1. Read config from <script id=\"app-config\"> JSON block\n2. initRepolero(ssss, repoUrlBase, env)\n3. Auto-set #\\_repoName text if element exists\n4. initMqStomp (skipped if opts.noMq or no StompJs global)\n5. registerUploadActions + registerRepoActions (if mqBridge)\n6. player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)\n7. opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }\n\n```\n\n**opts:**\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |\n\n### Config injection: `<script id=\"app-config\">`\n\nEach page has one jq expression that produces all config as JSON:\n\n```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```\n\nNote: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.\n\n### Result per page\n\n**view.jq.html** — full app, all layers (Alpine.js via CDN):\n\n```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss);\n</script>\n```\n\n**rc.jq.html** — paper-only, MQ logging, D3 MSC chart:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```\n\n**home.jq.html** — landing page, no player, no MQ:\n\n```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";\n\n  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```\n\n### TODO: Production optimizations (Brotli + Minify)\n\nThe dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.\n\n**Brotli compression** — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.\n\n**JS minification** — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:\n\n```sh\n# install esbuild (once)\nnpm i -g esbuild\n\n# minify all .js files in current folder and below\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```\n\n**Combined** (minify + pre-compress for Caddy static serving):\n\n```sh\n# minify + generate .br files\nfind . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;\n\n# then in Caddyfile, use:\n# file_server { precompressed br gzip }\n```\n\nNote: these commands modify files in-place. Run on `dist/` copies, not on `source/`.\n\n# Split player.js into modules\n\n## Done\n\n- [x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys\n- [x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper\n- [x] `player-state.js` (~43 lines) — resetState, resetMachine\n- [x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile\n- [x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus\n- [x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree\n- [x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper\n- [x] `player-edit.js` (~233 lines) — renderSave, renderEdit\n- [x] `player-space.js` (~253 lines) — resetSpace, renderSpace\n- [x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)\n  - imports from all 9 modules\n  - keeps: boot(), player() shell, notyf setup, PROXY assembly\n  - renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace\n  - bootS4 callback: glass init, mq subscribe, persistent elements, window.\\* hooks\n  - dropped dead code (keyNav), commented-out blocks, duplicate functions\n  - reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload\n  - player-mq.js updated to use PROXY.reloadCurrentPaper.fn()\n\n# Touchbar & Double-Touch\n\n## Done\n\n- [x] Alpine.js replaced Litedom for touchbar\n- [x] Double-touch changed from 2-finger simultaneous to **double-tap** pattern (400ms timeout)\n- [x] Native click delegation instead of Alpine @click (avoids double-fire)\n- [x] Toolbar buttons: click = primary action, double-touch = `action_extra`\n- [x] Mobile paste: uses native `prompt()` (no nice dialog, but works)\n- [x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips\n- [x] `data-doubletouch` only rendered when `action_extra` exists\n- [x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)\n- [x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)\n- [x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar\n- [x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:\n  - omitted / `false` / non-numeric (default) → **keep open** (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)\n  - `hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; **global last-click-wins** — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown\n  - Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`\n- [x] **`window.touchbarShow(definition, position)`** — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.\n\n## Known gaps\n\n- No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)\n- `initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check\n\n# Tabler\n\n## Done\n\n- [x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)\n- [x] `div.tabulator-tableholder` auto-shrinks for short tables: requires **both** `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)\n- [x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup\n- [x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-*`, `--font-color-*`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast\n- [x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order\n\n# Eisenhower matrix\n\n## Done\n\n- [x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.\n- [x] **Conflict lines** (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.\n- [x] **Font family on Vega text marks** — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.\n- [x] **`gravityToCorner` mode** (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.\n- [x] **Quasi-square grid in gravity mode** — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.\n- [x] **Stretch to span box** — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.\n- [x] **Label clearance** — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.\n- [x] **2 px quadrant gap** — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.\n- [x] **`harmonizeColors` helper** — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.\n- [x] **Density culling fontSize re-evaluation** — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.\n- [x] **`showBorder` box width fix** — Two corrections:\n  - **First pass (all layout engines):** `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.\n  - **Second pass (eisenhover-matrix.js positioning loop):** The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a **separate Vega text mark** at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).\n- [x] **StdDev-based density culling** — Replaced simple ascending-score drop with two-pass culling per quadrant:\n  1. Compute `mean` and `stdDev` of task scores in quadrant\n  2. **First pass:** Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`\n  3. **Second pass:** If still over threshold, drop lowest remaining non-outliers\n  4. Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`\n  - Controlled by `config.densityOptimum` (default `0.6`)\n  - `_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar\n- [x] **Font scale cap now width-proportional** — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.\n\n## Use\n\n- `player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.\n- Called from:\n  - `commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.\n  - `commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.\n\n## Touchbar Actions\n\n| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |\n\n## Files Changed\n\n- `ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern\n- `ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`\n- `player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`\n- `codeditor/src/databox.js`: mobile paste detection\n\n---\n\n## Testing\n\n```sh\n# Unit/snapshot tests (Jest + ESM)\nnpm test\n\n# E2E (Playwright)\ncd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```\n\nJest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/__snapshots__/`.\n\n## Key conventions\n\n- **ES modules** (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS\n- **No TypeScript** — pure JS with JSDoc\n- **Prettier** (`npx prettier`) with `semi: false` (except `.test.js` files)\n- **No linter or typechecker** configured\n\n## Voting system\n\n- SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)\n- `STATE.remoteSqlite.execSql(sql)` — uses **string interpolation** in SQL (NOT `?` placeholders)\n  - Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`\n  - Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them\n- Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)\n- `fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`\n  - **Do NOT** use full filesystem paths (e.g., `/home/looper/.../paper/...`)\n  - **Do** strip `#/paper/` prefix from URIs\n  - **Do** sanitize trail segments via `ssss.string.sanitizeName`\n- **Campaign prefix**: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.\n- **Vote helper**: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.\n- **Fragment ref helper**: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).\n- **JSON options loader**: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.\n- **Dialog-campaignes**: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.\n- **After voting**, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.\n\n## Touchbar system API\n\n- `window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show\n- `window.touchbarDispatch(action, element)` — button click dispatch\n- `data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap\n- `data-tooltip` — attribute for click/touch triggered toolbar\n- Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)\n- `$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types\n- **resultAction dispatch** (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.\n\n## Architecture quirks\n\n- `window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).\n- `proxy.STATE` is the mutable global state object (passed by reference).\n- Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.\n- Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.\n- Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.\n- `[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.\n- **`echoOut`** (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).\n\n## Heading-changes / Fragment management\n\n### Files changed\n\n- `player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`\n- `player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter\n- `proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched\n\n### Dialog rename\n\n`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.\n\n### hasMetaData filter (heading report)\n\n`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.\n\n`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.\n\nDelete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.\n\n### fragmentUrl system\n\nFormat: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).\n\n**Functions:**\n\n- `validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)\n- `loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:\n  1. Validates fragment URL format\n  2. Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)\n  3. Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)\n  4. Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix\n  5. Determines range end:\n     - `deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content\n     - `deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary\n  6. Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`\n  7. Slices `lines[start..end]` (start = lineNum - 1)\n  8. Returns `{ text, pathInRepo, startLine, endLine }` or `null`\n\n- `relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.\n\n### replantRef touchbar action\n\nTriggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:\n\n1. Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)\n2. Checks `route.isInEditor` — only works in edit layer\n3. Validates via `validateFragmentUrl(t)`\n4. Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`\n5. Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line\n6. Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor\n7. Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`\n\nKey: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.\n\n### URL construction\n\nFollowing `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).\n\n### SQL rows\n\n`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.\n\n## OuchDB (Remote CouchDB/PouchDB)\n\n**Adapter:** `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.\n\n**Wiring:** `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:\n\n```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```\n\nThe `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).\n\n**Protocol config:** `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.\n\n**Usage (vegaLinks persistence):**\n\n- `loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array\n- `saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`\n- Guard: `if (!STATE.remoteOuchdb) return`\n- Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)\n\n**Adapter API:** `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).\n\n**`update()` method:** GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.\n\n### CORS conflict: Caddy + PouchDB double headers\n\n`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:\n\n```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```\n\nChrome reads the **first** `Access-Control-Allow-Origin` (`*`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `*` with credentials.\n\n**Fix:** Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:\n\n```json\n{ \"httpd\": { \"enable_cors\": false } }\n```\n\nStart with `--config` flag:\n\n```\nnpx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json\n```\n\n## eisenhover-matrix.js — Vega spec builder\n\n**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`\n\n**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object.\n\n**Config options:**\n\n| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| `width`               | number  | 600     | Spec width                                                                             |\n| `height`              | number  | 500     | Spec height                                                                            |\n| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |\n| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |\n\n**Path utilities:**\n\n- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering).\n- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point.\n\n**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes.\n\n## Traps pattern (cleanup on route leave)\n\n`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:\n\n- `getCommandHandler()` creates a `traps = []` array per invocation\n- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`\n- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`\n- On route change, `cleanup()` iterates through all traps and calls each one `await`\n- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`\n\n**Current trap users:**\n\n| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |\n| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |\n| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |\n| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |\n| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |\n\n**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages.\n\n**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore.\n\n## cmdVotingCampaign.js — link persistence","outerHtml":"<p>;{ engine:dot , rankdir:LR, applyCss:dark, useGvPack:true, clusterize:false }</p>\n\n<h1>player.js - Analysis & Summary</h1>\n\n<h2>Overview</h2>\n\n<p>`player.js` is the main orchestrator of the <b>repolero-prototype</b> app (~3600 lines). It is a single-page application with hash-based routing, four visual layers (<b>glass</b>, <b>paper</b>, <b>space</b>, <b>edit</b>), and real-time collaboration via STOMP/RabbitMQ.</p>\n\n<p>The HTML shell (`view.jq.html`) defines the DOM skeleton. `player.js` brings it alive.</p>\n\n<h2>repolero-prototype — UI concept (from repolero-prototype.mdd)</h2>\n\n<h3>Design principles</h3>\n\n<ul><li><b>Minimal layout</b> (water.css-inspired)</li><li><b>Mouse-less interaction</b> — keyboard-first</li><li><b>Keyboard shortcuts follow key ergonomics, not semantics</b> — ESC, F1–F5, TAB, arrows, SPACE, ENTER, BACKSPACE</li></ul>\n\n<h3>Three layers</h3>\n\n<p>| Layer      | Purpose                             | Implementation                                 |\n| ---------- | ----------------------------------- | ---------------------------------------------- |\n| <b>space</b>  | Map view                            | player/map-show.js (OpenLayers)                |\n| <b>paper</b>  | Notebook content                    | renderLayerMode → fetch + mdd/html/json render |\n| <b>editor</b> | Edit content                        | codeditor.js (CodeMirror 6)                    |\n| <b>glass</b>  | UIX overlay (search, menu, actions) | Alpine stores + Autocomplete                   |</p>\n\n<h3>Glass menu selector</h3>\n\n<p>Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete</p>\n\n<h3>Visualization</h3>\n\n<ul><li><b>Vega-Lite</b>: parallel coords, word cloud, radar, radial tree, calendar, heatmap</li><li><b>D3</b>: hierarchy pack, message sequence chart (rc page)</li></ul>\n\n<h3>Data dynamics</h3>\n\n<ul><li>Webhooks: repo-sync, map-show tile generation</li><li>Portlets concept (TODO)</li><li>Singularity/plurarity: repolero subsystem of fragments and stages (TODO)</li></ul>\n\n<h3>UI tricks</h3>\n\n<ul><li>tippy.js for tooltips</li><li>icomoon icons as font</li></ul>\n\n<h2>ssss — super simple static site generator</h2>\n\n<p>repolero-prototype is built with <b>ssss</b>, a custom static site generator.</p>\n\n<h3>Principles</h3>\n\n<ul><li><b>Petri-net flow</b>: many sources flow into one output, many outputs flow into one deployment</li><li><b>JSON inheritance (meld)</b>: configs merge via jq `meld()` — e.g. `cat root.jq.json admin.config.json | jq -s 'meld(.[0]; .[1])'`</li></ul>\n\n<h3>MPA concept</h3>\n\n<p>Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers.</p>\n\n<h3>Folder structure</h3>\n\n<p>```\nssss/\n  assets/\n    images/*.png\n    styles/*.less\n    scripts/lib.js          — ssss core client-side JS (ssss.js)\n    scripts/*.js\n  data/*                    — primary data (e.g. nljson)\nsource/\n  s4.config.json            — run ssss-run from here\n  *.jq.json                 — declares a page to render\n  *.jq.html                 — template (or linked from theme via jq.json)\n  <i>                         — adjacent to ssss/</i>\noutput/ (aka dist/)\n  assets/                   ← ssss/assets + source/assets merged\n  <i>.html                    ← source/</i>.jq.json + *.jq.html rendered\n  data/<i></i>                   ← sfsDir + tot\ndeploy/\n  rsync mirror of output/   — final destination (many sources converge)\n```</p>\n\n<h3>Tech choices (from ssss.mdd)</h3>\n\n<p>| Category           | Winner / in use             | Alternatives considered                          |\n| ------------------ | --------------------------- | ------------------------------------------------ |\n| CSS framework      | MVP.css, Pure.css           | matcha, sakura, bulma, milligram, open-props     |\n| SPA / JS framework | Alpine.js, tint, yoffee     | lit, hybrids, stimulus, petite-vue, sprae, crank |\n| Hash routing       | Hash-Router (Michael Sogos) | wand, easyrouter, stateman                       |\n| State management   | valtio                      | zustand, jotai, xstate                           |\n| Key navigation     | keymage, gridnav            | keydrown, keycharm, a11yarrows                   |\n| Charts             | Vega-Lite                   | —                                                |\n| Drag & drop        | dragula                     | —                                                |\n| Notifications      | humane-js (Notyf in player) | —                                                |\n| Search             | typesense, algolia          | —                                                |\n| Modal              | tingle, micromodal          | —                                                |\n| Fetch              | TwinSpark, aja.js           | —                                                |\n| JSON rendering     | jsonToHtml-render           | —                                                |\n| Store              | nbubna/store                | websheet, firebase                               |\n| GUI layout         | dockview                    | —                                                |</p>\n\n<h3>Usage</h3>\n\n<p>`yarn serve`, `yarn watch`, `yoffee`</p>\n\n<h2>Build Pipeline</h2>\n\n<p>Each `.jq.html` template in `source/` has a corresponding rendered `.html` in `dist/`. Each template also has a paired `.jq.json` config file that provides its data context (site title, env, theme mode, head scripts/links, menu pages, hotkeys, etc.):</p>\n\n<p>```\nsource/repolero-prototype/view.jq.json  →  data for view.jq.html\nsource/repolero-prototype/view.jq.html  →  dist/repolero-prototype/view.html</p>\n\n<p>source/repolero-prototype/rc.jq.json    →  data for rc.jq.html\nsource/repolero-prototype/rc.jq.html    →  dist/repolero-prototype/rc.html</p>\n\n<p>source/repolero-prototype/home.jq.json  →  data for home.jq.html\nsource/repolero-prototype/home.jq.html  →  dist/repolero-prototype/home.html\n```</p>\n\n<p><b>All jq.html pages:</b>\nview, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants)</p>\n\n<p>`reflect.jq.html` (545 lines, paired with `reflect.jq.json`, title `repolero:reflect`) is the <b>alternative player shell</b> introduced for tenant subdomains. Mode is the full feature set (`admin,self,water,grid,icons,autocomplete,keynav,dynamic,vega,mq,dark,touch,table,2d`), so it boots the same `player()` as `view.jq.html` but with a different layout/skin tailored for tenant entry. Caddy serves `/reflect.html` (not `/view.html`) for the subset of tenant subdomains opted into the reflect layout — see `linode-server/caddy/local/Caddyfile.local.caddyfile` and `…/linode/Caddyfile.contabo.caddyfile`. Currently the `makata` tenant uses `/reflect.html`; other tenants (`massage`, `maxina`, …) still fall through to `/view.html`.</p>\n\n<p><b>The .jq.json structure</b> (shared across pages):</p>\n\n<p>```\n{\n  \"name\": \"open-city\",\n  \"repoName\": \"repolero\",\n  \"protocolRouter\": \"¦s4protocolRouter¦\",   // resolved at build\n  \"site\": {\n    \"ver\", \"env\", \"lang\", \"theme\",\n    \"mode\": \"admin,self,water,...\",          // feature flags\n    \"title\": \"repolero:view\",\n    \"logoMini\", \"fragments\", \"menu.pages\",\n    \"hotkey\": { eMain, glassToggle, ... },   // per-page hotkey config\n    \"touchbar\": { paper: [...svgs] }\n  },\n  \"head\": {\n    \"link\": [ { href, rel, type, tag } ],   // CSS deps\n    \"script\": [ { src, type, tag } ]         // JS deps\n  },\n  \"root\": \"¦.untiloop.repolero¦\"            // resolved at build\n}\n```</p>\n\n<p>The `mode` field controls which CSS/JS libs are included via `s4FilterIncludes($input)`. E.g. rc.jq.json has no `vega` in mode, so Vega-Lite scripts are excluded from its `<head>`.</p>\n\n<p>The jq-html engine runs server-side and resolves all `¦expr¦` expressions into concrete values. The browser only ever loads the `dist/*.html` files — it never sees jq syntax.</p>\n\n<p><b>What gets transformed:</b></p>\n\n<ul><li>HTML attributes: `href='¦\"assets://x\"|s4url¦'` → `href='http://assets.untiloop.loc/x'`</li><li>Text content: `<title>¦.site.title¦</title>` → `<title>repolero:rc</title>`</li><li>Element iteration: `<option ¦=\".items|.[]\">` → N `<option>` elements</li><li>Inline JSON: `JSON.parse('¦.site.env|tojson¦')` → `JSON.parse('\"loc\"')`</li></ul>\n\n<p><b>What passes through untouched:</b></p>\n\n<ul><li>`<script type=\"module\">` JS code (the jq-html engine only interpolates `¦¦` tokens inside it)</li><li>Alpine.js syntax: `x-data`, `x-for`, `x-show`, `x-text`, `$store.glass.*`</li><li>Runtime attributes: `resolveProps`, `persistent`, `data-hotkey`</li></ul>\n\n<h2>jq-html Templating</h2>\n\n<p>`view.jq.html` uses a custom templating engine called <b>jq-html</b>. It embeds jq expressions directly in HTML using `¦` (broken bar) delimiters.</p>\n\n<p><b>Three modes:</b></p>\n\n<p>| Syntax                       | Where                               | Effect                         |\n| ---------------------------- | ----------------------------------- | ------------------------------ | ----------------------- |\n| `¦expr¦` in attribute value  | e.g. `src='¦\"assets://x\"            | s4url¦'`                       | Replaced with jq result |\n| `¦expr¦` in text content     | e.g. `<title>¦.site.title¦</title>` | Replaced with jq result        |\n| `¦=\"expr\"` as attribute name | e.g. `<option ¦=\".items\\|.[]\">`     | Element repeated per jq output |</p>\n\n<p>The iteration form `¦=\"expr\"` clones the host element for each value the jq expression yields. Inside the cloned element, `.` refers to the current item. Example: `<div ¦='[1,2,3]|.[]'>` produces three `<div>` elements.</p>\n\n<p><b>Common helpers used in the template:</b></p>\n\n<p>| Helper                     | Purpose                                                          |\n| -------------------------- | ---------------------------------------------------------------- |\n| `s4url`                    | Resolves protocol URIs (`assets://`, `repo://`, `crud://`, etc.) |\n| `s4FilterIncludes($input)` | Filters theme config arrays by inclusion rules                   |\n| `tojson`                   | Wraps result in JSON for safe embedding into JS vars             |</p>\n\n<p><b>How the template feeds player.js</b> (in the boot `<script>`):</p>\n\n<p>```\nconst env = JSON.parse('¦.site.env|tojson¦')\nconst siteHotkey = JSON.parse('¦.site.hotkey|tojson¦')\nconst mqBrokerURL = JSON.parse('¦\"mq:/ws\"|s4url|tojson¦')\n...\n```</p>\n\n<p>Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into `player({...})`.</p>\n\n<h2>How It Works (HTML + JS together)</h2>\n\n<h3>The DOM Skeleton (view.jq.html)</h3>\n\n<p>The HTML defines five top-level containers, only one visible at a time:</p>\n\n<p>```\n<body class=\"spa frozen\">\n  #status          Alpine-bound status bar (TL = top-left, BL = bottom-left)\n  #space > #map    OpenLayers map container (starts .off)\n  #paper           empty div, filled dynamically (starts .on)\n  #edit > #editor  CodeMirror textarea (starts .off)\n  #glass           search/command overlay (starts .off)\n    .menu-navigator    nav buttons: edit, space, glass-toggle\n    .menu-content      sections: filters, search, actions, history, options, .secret, console, links\n  #alpine-touchbar Alpine-bound toolbar (persistent, x-cloak until shown)\n</body>\n```</p>\n\n<p>Layers are toggled via `.on` / `.off` CSS classes by `eSetOnOff()`.</p>\n\n<h3>Boot Sequence</h3>\n\n<p>```\nview.jq.html <script type=\"module\"></p><ol><li>jq-html templates inject server config into JS vars\n     (env, siteVer, siteHotkey, all URL bases, protocolRouter)</li><li>initRepolero(ssss, repoUrlBase, env)    -> idBui, theme, mobile vh</li><li>ssss.initMqStomp(ssss, StompJs, {...})  -> stompClient + exFanout</li><li>player({ssss, ...all config...})        -> boots the app\n```</li></ol>\n\n<h3>Inside player()</h3>\n\n<p>```\nplayer()\n  |  STATE init: repoName, history, indexJson, ast, last-hashes\n  |  Notyf init (toaster)\n  |  PROXY assembly (shared deps bag)\n  |\n  |-- ssss.bootS4({ routeFallback, defaultRoute }, bootS4callback)\n  |     |\n   |     |-- bootS4callback() runs once:\n   |     |     - resetMachine() -> fetch .repo.json, init space map\n   |     |     - initGlassMenu() -> Alpine store + Autocomplete on #menu-list\n   |     |     - initGlassStatus() -> Alpine store on #status\n   |     |     - addOneTimeTweaksTodo() -> DOM patches, touchpad, hotkeys\n  |     |     - MQ subscribe (stompClient.subscribe -> mqFanoutConsumer)\n  |     |     - persistent elements: restore checkboxes/details from localStorage\n  |     |     - global hotkeys via keymage\n  |     |     - window.* function assignments\n  |     |\n  |     |-- routeFallback(tokens) runs on every hash change:\n  |           parses: #/{layer}/{...path}[@command]\n  |           calls:  renderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |           stores: history, last route\n```</p>\n\n<h3>renderLayerMode - the core switch</h3>\n\n<p>This is the heart. It receives a `targetLayer` and toggles the DOM:</p>\n\n<p>```\nrenderLayerMode(STATE, currentLayer, targetLayer, command, tokens)\n  |\n  |-- unbind previous hotkeys (trapsLayer cleanup)\n  |-- set body[data-mode] = targetLayer\n  |\n  |-- switch(targetLayer):\n  |\n  |   'glass':\n  |     show #glass, show #portal.console\n   |     updateGlassState() -> refresh Alpine store data (papers, history, nodes)\n  |     focusMenuInput() -> apply armed search value, focus #menu-input\n  |     if @query command: arm search input with query string\n  |     bind hotkeys for #glass + #main-menu\n  |\n  |   'paper':\n  |     show #paper, hide #glass/#edit/#space\n  |     update #e-main -> links to #/space\n  |     update #e-secondary -> links to #/edit/{same-file} (if webhook on)\n  |     if @command (dashboard, denied, login, demo):\n  |       getCommandHandler() or dashboard() renders into #paper\n  |       reloadCurrentPaper = the reload function (kept for MQ reload)\n  |     else (normal file):\n  |       fetch file via fragmentUrl, render HTML/mdd/json into #paper\n  |       tabler() adds heatmaps to tables\n  |       observeSubTree() tracks visible headings for status bar\n  |     bind hotkeys for #paper\n  |\n  |   'edit':\n  |     show #edit, hide others\n  |     update #e-secondary -> \"save\" link to #/save/paper/{file}\n  |     fetch file content, initEditor() -> CodeMirror 6 in #editor\n  |     restore cursor position from localStorage\n  |     wire editor events: save, autocomplete, inspect, larry macros\n  |     bind hotkeys for #edit\n  |\n  |   'space':\n  |     show #space, hide others\n  |     if no tokens: restore last space hash from history\n  |     else: mapShowCtrl.gotoPosition(mapName) + gotoNodeId if specified\n  |     bind hotkeys for #space\n  |\n  |   'save':\n  |     savePaper() -> editSaveWithWebhook() via POST to webhook\n  |     on success: navigate back to #/paper/{file}\n  |\n  |-- initToolTipBar() -> re-render toolbar tooltips\n  |-- focus the new layer element\n```</p>\n\n<h3>How the HTML sections map to player.js functions</h3>\n\n<p>| HTML element              | player.js handler                                                            |\n| ------------------------- | ---------------------------------------------------------------------------- |\n| `#menu-input`             | `initGlassAutocompleteInput()` -> Autocomplete lib -> `glassSearchRun()`     |\n| `#menu-list` (slot)       | `initGlassMenu()` -> Alpine store + template with history, papers, fragments |\n| `#status` (slot)          | `initGlassStatus()` -> Alpine store with statusTL/statusBL                   |\n| `#alpine-touchbar`        | `initToolTipBar()` -> Alpine x-for toolbar with icon buttons                 |\n| `#paper`                  | `renderLayerMode('paper')` fills innerHTML (mdd/html/dashboard)              |\n| `#editor`                 | `renderLayerMode('edit')` -> `initEditor()` -> CodeMirror 6                  |\n| `#map`                    | `resetSpace()` -> `mapShow()` -> OpenLayers in #map                          |\n| `#e-main`                 | dynamically updated: text=\"paper\"/\"space\", href=last opposite layer          |\n| `#e-secondary`            | dynamically updated: \"edit\"/\"save\" depending on layer + webhook              |\n| `[persistent]` elements   | event handler restores/saves checkbox/details state via localStorage         |\n| `[data-hotkey]` elements  | `injestHotkeys()` scans + binds via keymage per layer                        |\n| `[data-tooltip]` elements | `initToolTipBar()` -> touch/hover tooltip rendering                          |\n| `[resolveProps]` elements | `addOneTimeTweaksTodo()` patches href/value/text with runtime vars           |\n| `onclick=emitRepo*`       | `window.emitRepoActualize/Index/Sync/Mail` -> `emitMqMessage()`              |\n| `onclick=closeGlass`      | `window.closeGlass()` -> `goToPrev(STATE)`                                   |</p>\n\n<h3>Data Flow: Search</h3>\n\n<p>```\nuser types in #menu-input\n  -> Autocomplete.onSearch({ currentValue })\n  -> glassSearchRun(queryString, ...)\n       analyzeQuery() parses sources + terms (history|files|fragments)\n       |-- searchInHistory()    -> filters STATE.history in-memory\n       |-- searchInRepoJson()   -> filters STATE.indexJson.papers with glob/text\n       |-- searchInSql()        -> SQL query via remoteSqlite (debounced)\n       returns reducedResult[]\n  -> Autocomplete.onResults(matches) renders <li> items\n  -> Autocomplete.onSubmit(object) -> gotoUrl(uri)\n```</p>\n\n<h3>Data Flow: MQ (real-time sync)</h3>\n\n<p>```\nstompClient.subscribe(exFanout, mqFanoutConsumer)\n  incoming message:\n    reload: 'index'  -> resetMachine() (re-fetch .repo.json, re-init space)\n    reload: 'paper'  -> reloadCurrentPaper() if viewing that paper\n    reload: 'map'    -> window.location.reload()\n    reload: { space } -> resetMachine() + refreshTiles()\n    verb: 'goto:paper' -> gotoHash(#/paper/{fn}:{location})</p>\n\n<p>outgoing (if propagation-*-out checkboxes are on):\n    space position changes -> emitFanoutMessage(payload)\n    paper double-touch     -> emitFanoutMessage({ verb:'goto:paper', fn, location })\n    actions panel          -> emitMqMessage('REPO_AFFERENT'|'REPO_SERIAL', {...})\n```</p>\n\n<h3>Persistence Model</h3>\n\n<p>All state is scoped by `windowStoreKey(key)` which prefixes with `{idBui, repo}`:</p>\n\n<p>| Key pattern                          | What it stores                         |\n| ------------------------------------ | -------------------------------------- |\n| `@lastRoute`                         | last visited hash                      |\n| `@lastSearchInputValue`              | last search query                      |\n| `@lastGlassState`                    | was glass open? (bool)                 |\n| `history`                            | array of visited hashes                |\n| `editorPos:{uri}`                    | cursor/selection for each file         |\n| `@theSTATE.ast.space.mapName`        | last viewed map name                   |\n| `@theSTATE.ast.space.position:{map}` | last map viewport                      |\n| `options/glass/*`                    | all checkbox states from options panel |\n| `.theme`                             | light/dark (no repo prefix)            |\n| `.secret/glass/portal-token`         | auth token (no repo prefix)            |</p>\n\n<h2>ssss.js - The Utility Library</h2>\n\n<p>`ssss.js` (`/source/assets/s4/ssss.js`, ~1540 lines) is the shared utility layer used by player.js and the rest of the app. It is imported as `import ssss from '...s4/ssss.js'` and passed around via the `player({ssss, ...})` call.</p>\n\n<h3>What ssss provides to player.js</h3>\n\n<p>| Category            | Functions / Objects                                                                                | Used for                                                                              |\n| ------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| <b>Boot</b>            | `bootS4()`                                                                                         | Hash-router setup, route registration, `routeFallback`, initial navigate              |\n| <b>Fetch</b>           | `fetchJson()`, `fetchWithETag()`, `fetchWithTimeout()`, `fetchHead()`, `fetchAny()`, `fetchHtml()` | All HTTP with ETag caching in localStorage                                            |\n| <b>Protocol URLs</b>   | `Url(url)`                                                                                         | Resolves `assets://`, `repo://`, `crud://`, `mq://` etc. via `protocolRouter` map     |\n| <b>MQ</b>              | `initMqStomp()`                                                                                    | STOMP/WebSocket client init, returns `{stompClient, exFanout, exQueue, exTopic}`      |\n| <b>Rendering</b>       | `mddRender()`, `jsonRender()`, `htmlRender()`, `renderCoreUi()`, `renderMarkdown()`                | Render `<mdd>`, `<json>`, `<htm>` tags into HTML                                      |\n| <b>Webhooks</b>        | `createWebHookAction()`, `emitActionWithWebhook()`, `createMqMessageAction()`                      | Webhook POST with streaming response, MQ publish helpers                              |\n| <b>DOM</b>             | `getNodeTrail()`, `getDeepParents()`, `addTrackedEventListener()`, `removeEventListeners()`        | DOM traversal, tracked event binding/cleanup                                          |\n| <b>Navigation</b>      | `sakaInit()`, `insertTooltips()`, `clearTooltips()`                                                | Keyboard nav overlays (Saka), hotkey badge tooltips                                   |\n| <b>Misc</b>            | `debounce()`, `sleep()`, `isMobileAppMode()`, `scaleValues()`, `formatDuration()`, `logMsg()`      | Common utils used everywhere                                                          |\n| <b>String</b>          | `string.*` (namespace)                                                                             | `ConvertToAscii()`, `commonPathPrefix()`, `uuidv4()`                                  |\n| <b>Parsing</b>         | `mddToAst()`, `acorn`, `sexp`, `glob`                                                              | MDD AST, JS expression parsing, S-expressions, glob-to-regex                          |\n| <b>Components</b>      | `ObjectId`, `Larry`, `RemoteSqlite`, `RemoteState`, `initDropZone`                                 | MongoDB-style IDs, grammar validator, HTTP SQLite adapter, socket.io state, drag-drop |\n| <b>Custom Elements</b> | `S4Unit`, `S4Json`, `S4Vega`, `S4Tint`                                                             | Web components registered at boot via `initCustomElements()`                          |</p>\n\n<h3>ssss Sub-modules</h3>\n\n<p>```\nssss.js\n  |-- modules/\n  |     |-- string.js          : ConvertToAscii, commonPathPrefix, uuidv4\n  |     |-- mdd.mjs            : mddToHtml, mddToAst (remark + markdown-it)\n  |     |-- glob.js            : globToRegExp\n  |     |-- sexp.js            : S-expression parser\n  |     |-- acorn.min.js       : JS expression parser (bundled)\n  |     |-- tooltip-overlay.js : insertTooltips, clearTooltips\n  |     |-- toc-nav/toc-nav.js : sakaInit (keyboard nav overlay)\n  |-- adapters/\n  |     |-- remote-sqlite.js   : RemoteSqlite class (HTTP CRUD for SQLite)\n  |     |-- remote-state.js    : RemoteState class (socket.io/Redis state)\n  |-- components/\n  |     |-- object-id.js       : ObjectId (MongoDB-style unique IDs)\n  |     |-- s4-larry.js        : TreeValidator, grammar utils for autocomplete\n  |     |-- s4-unit.js         : S4Unit base web component (HTMLElement)\n  |     |-- s4-json.js         : S4Json web component + jsonToHtml()\n  |     |-- s4-vega.js         : S4Vega web component (Vega-Lite)\n  |     |-- s4-tint.js         : S4Tint web component (Tint templates)\n  |     |-- drop-zone.js       : initDropZone (drag/drop/paste file upload)\n```</p>\n\n<h3>How ssss.bootS4() drives the app</h3>\n\n<p>```\nbootS4({ protocolRouter, routeFallback, defaultRoute }, fnBootStart)</p><ol><li>Set window.s4util (global: protocolRouter, ea, fetch helpers, Url)</li><li>Register custom elements (s4-unit, s4-json, s4-vega, s4-tint)</li><li>Wait for document.readyState === 'complete'</li><li>Register page routes (if any)</li><li>Call fnBootStart() -> player's boot callback</li><li>Router.init(null, routeFallback)  -> hash change handler</li><li>Router.navigate(defaultRoute)     -> first render\n```</li></ol>\n\n<p>The `routeFallback` is where player.js receives every hash change as parsed tokens, then calls `renderLayerMode()`.</p>\n\n<h3>Key design notes</h3>\n\n<ul><li><b>ETag caching</b>: `fetchWithETag` stores response bodies + ETags in localStorage. Uses `If-None-Match` header for 304 responses.</li><li><b>Protocol router</b>: `Url()` translates abstract URIs (`assets://`, `repo://`) to real URLs based on environment config injected by jq-html.</li><li><b>Tracked event listeners</b>: `addTrackedEventListener` stores references in `element.__listenerRegistry` so `removeEventListeners` can batch-remove them. This is how layer switching cleans up hotkeys and tooltips.</li><li><b>window.s4util</b>: global object set at boot with `protocolRouter`, `ea` (EventAggregator), and fetch helpers. Shared across all modules.</li></ul>\n\n<h2>Architecture</h2>\n\n<p>```\nplayer.js (orchestrator)\n  |-- repolero-init.js     : bootstrap, theme, idBui, mobile vh\n  |-- glass-search.js      : autocomplete search engine\n  |     |-- search/filter-base.js        : AST-to-SQL/JS filter core\n  |     |-- search/filter-fragments-pre.js  : SQL pre-filter (FragmentPreFilter)\n  |     |-- search/filter-fragments-post.js : JS post-filter (FragmentPostFilter)\n  |     |-- search/filter-files-pre.js   : SQL pre-filter for files\n  |     |-- search/filter-maker.js       : older duplicate of filter-base\n  |     |-- search/search-fragments.js   : SQL fragment search via remoteSqlite\n  |     |-- search/search-history.js     : in-memory history search\n  |     |-- search/search-repo.js        : repo index JSON search + glob/text filter\n  |     |-- search/search-space.js       : empty\n  |-- larry.js             : grammar-based autocomplete actor system\n  |-- navigation.js        : RBush spatial nav + DOM selection helpers\n  |-- commands.js          : @command handlers (denied, login, demo)\n  |-- codeditor.js         : CodeMirror 6 editor wrapper\n  |-- tabler.js            : table heatmap + DataTable integration\n  |-- visualization.js     : Vega-Lite + intensity timeline renderer\n  |-- ui-touchbar.js       : touch/gesture handling + tooltip bar\n  |-- alpine-stores.js     : Alpine.js stores (glass, darkMode)\n  |-- editor-flash.js      : CM6 flash highlight extension\n  |-- meditation-navigation.js : editor cursor history tracker\n  |-- actions.js           : shared window.* action registrations\n  |-- player/              : split modules\n  |     |-- map-show.js          : OpenLayers map for space view\n  |     |-- page-dashboard.js     : dashboard view with word cloud + tables\n  |     |-- page-file-manager.js  : file manager actions\n  |     |-- player-utils.js       : constants, route/URI helpers, hotkeys\n  |     |-- player-webhook.js     : isWebHookOn, upload, editSave, savePaper\n  |     |-- player-state.js       : resetMachine, STATE init\n  |     |-- player-mq.js          : mqFanoutConsumer, emitFanout/Mq/Tile\n  |     |-- player-glass.js       : initGlassMenu, initGlassStatus, autocomplete\n  |     |-- player-tweaks.js      : reactArmSelection, addOneTimeTweaksTodo\n  |     |-- player-paper.js       : eSetOnOff, renderPaper, createFilterPapers\n  |     |-- player-edit.js        : renderEdit, renderSave\n  |     |-- player-space.js       : renderSpace, resetSpace\n  |     |-- simple-datatables.js : DataTables library (bundled)\n```</p>\n\n<h2>Layers (routing)</h2>\n\n<p>| Hash prefix | Layer | Description                           |\n| ----------- | ----- | ------------------------------------- |\n| `#/glass/`  | glass | Search/command overlay                |\n| `#/paper/`  | paper | Document viewer (mdd, md, html, json) |\n| `#/edit/`   | edit  | CodeMirror 6 editor                   |\n| `#/space/`  | space | OpenLayers 2D map viewer              |\n| `#/save/`   | save  | Triggers paper save via webhook       |</p>\n\n<h2>File Summaries</h2>\n\n<p>| File                            | Lines | Role                                           |\n| ------------------------------- | ----- | ---------------------------------------------- |\n| view.jq.html                    | ~590  | Main HTML shell, full app (all layers)         |\n| view.jq.json                    | ~220  | Config: full mode, hotkeys, toolbar icons      |\n| reflect.jq.html                 | ~545  | Alternative player shell for tenant subdomains |\n| reflect.jq.json                 | ~207  | Config: full mode, `repolero:reflect` title    |\n| rc.jq.html                      | ~365  | RC page, paper-only, D3 MSC chart              |\n| rc.jq.json                      | ~65   | Config: minimal mode (no vega, no map-show)    |\n| home.jq.html                    | ~97   | Landing page, paper-only, no player() call     |\n| home.jq.json                    | ~75   | Config: minimal mode                           |\n| player.js                       | ~1022 | Main orchestrator, routing, boot, MQ, webhooks |\n| repolero-init.js                | ~160  | App init, theme, idBui, mobile viewport        |\n| glass-search.js                 | ~250  | Search dispatcher: history, files, fragments   |\n| larry.js                        | ~195  | Grammar-based autocomplete actors for editor   |\n| navigation.js                   | ~330  | RBush spatial index + DOM walk helpers         |\n| commands.js                     | ~75   | @denied, @login, @demo command handlers        |\n| codeditor.js                    | ~505  | CM6 editor: save, blame, autocomplete, macros  |\n| tabler.js                       | ~329  | Heatmap tables + DataTable + markdown export   |\n| visualization.js                | ~220  | Vega-Lite embed + intensity timeline           |\n| ui-touchbar.js                  | ~682  | Touch gestures, tooltip bar, toolbar icons     |\n| alpine-stores.js                | ~32   | Alpine.js stores (glass, darkMode)             |\n| editor-flash.js                 | ~50   | CM6 flash highlight decoration                 |\n| meditation-navigation.js        | ~100  | Editor cursor history (back/forward)           |\n| actions.js                      | ~100  | Shared window.* action registrations          |\n| search/filter-base.js           | ~300  | AST expression to SQL/JS conversion            |\n| search/filter-fragments-pre.js  | ~100  | SQL WHERE builder for fragments                |\n| search/filter-fragments-post.js | ~80   | JS post-filter for fragment rows               |\n| search/filter-maker.js          | ~80   | Older duplicate of filter-base                 |\n| search/filter-files-pre.js      | NEW   | SQL WHERE builder for files                    |\n| search/search-fragments.js      | ~120  | SQLite fragment search orchestration           |\n| search/search-history.js        | ~50   | History search by term matching                |\n| search/search-repo.js           | ~120  | Repo JSON index search + glob/text filter      |\n| search/search-space.js          | 1     | Empty                                          |\n| player/map-show.js              | ~1958 | OpenLayers tile map + SVG overlays             |\n| player/page-dashboard.js        | ~635  | Dashboard: word cloud + tables from SQLite     |\n| player/page-file-manager.js     | ~52   | File manager actions                           |\n| player/player-edit.js           | ~388  | renderEdit, renderSave                         |\n| player/player-glass.js          | ~324  | initGlassMenu, initGlassStatus, autocomplete   |\n| player/player-mq.js             | ~163  | mqFanoutConsumer, emitFanout/Mq/Tile           |\n| player/player-paper.js          | ~745  | eSetOnOff, renderPaper, createFilterPapers     |\n| player/player-space.js          | ~264  | renderSpace, resetSpace                        |\n| player/player-state.js          | ~69   | resetMachine, STATE init                       |\n| player/player-tweaks.js         | ~301  | reactArmSelection, addOneTimeTweaksTodo        |\n| player/player-utils.js          | ~378  | Constants, route/URI helpers, hotkeys          |\n| player/player-webhook.js        | ~130  | isWebHookOn, upload, editSave, savePaper       |\n| player/simple-datatables.js     | ~6993 | DataTables library (bundled)                   |</p>\n\n<h2>Layout Engines (Eisenhower Matrix)</h2>\n\n<p>| Layout                                       | Key Distinction                                                                     |\n| -------------------------------------------- | ----------------------------------------------------------------------------------- |\n| <b>normal</b> (`layout-tasks-in-box-normal.js`) | Simple row-packer, corner-mirror gravity, now also uses `topoSort`                  |\n| <b>boss</b> (`like-a-boss.js`)                  | Force-directed spring+repulsion physics                                             |\n| <b>boss2</b> (`like-a-boss2.js`)                | Deterministic grid packer, `topoSort` direction-aware                               |\n| <b>boss3</b> (`like-a-boss3.js`)                | ngraph.forcelayout physics engine                                                   |\n| <b>boss4</b> (`like-a-boss4.js`)                | Vizdom (Graphviz/Dagre) hierarchical layout — best for directed graphs              |\n| <b>boss5</b> (`like-a-boss5.js`)                | WebCoLA constraint-based layout with overlap removal — currently active/recommended |</p>\n\n<p>All active layout engines (boss2–5, normal) set two width fields per task:</p>\n\n<ul><li>`taskWidth` = `estimateTextWidth(task, fontSize, fontWeight)` — task text only</li><li>`width` = `estimateTextWidth(miniLabel ? task + \", \" + miniLabel : task, fontSize, fontWeight)` — task + optional miniLabel</li></ul>\n\n<h2>Found TASK: Comments</h2>\n\n<p>| File       | Line | Task                                   |\n| ---------- | ---- | -------------------------------------- |\n| rc.jq.html | ~219 | Empty `// TASK:` in execState function |</p>\n\n<p>Note: The autoZoom TASK in map-show.js has been resolved.</p>\n\n<h2>Theme structure</h2>\n\n<p>Two LESS files serve the same HTML with different skins:</p>\n\n<ul><li>`theme-bare/repolero.less` — full-featured \"view\" skin (used by `view.jq.html`, `root.jq.html`, `home.jq.html`, `rc.jq.html`)</li><li>`theme-bare/reflection.less` — compact \"reflect\" skin (used by `reflect.jq.html`)\n  Both import `theme-bare/repolero-effect.less`.</li></ul>\n\n<p><b>Key structural difference resolved:</b> `reflection.less` now defines the `.normalizeMddNormal()` mixin (same as `repolero.less`) and calls it in both `#paper` and `dialog.s4-dialog article.paper` contexts, instead of having those styles inlined. Remaining visual differences: tighter spacing, hidden notyf/status bar, glowier active rows, different gradient angles.</p>\n\n<h3>Dialog backdrop blur</h3>\n\n<p>`water-hack.less` (line 1524) applies `backdrop-filter: blur(4px)` to `<dialog>::backdrop`. When any `<dialog>` opens via `.showModal()`, the background behind it is blurred + dimmed (`#0000009c`). This covers all native `<dialog>` elements (campaign select, heading-changes, etc.).</p>\n\n<h2>Observations</h2>\n\n<ol><li><b>player.js is large</b> (~1022 lines). `renderLayerMode` handles all layer switching in one switch.</li><li><b>PROXY/proxy pattern</b> — see detailed analysis below.</li><li><b>Commented-out code</b> - present in player.js and ui-touchbar.js.</li><li><b>search/filter-maker.js</b> appears to be an older duplicate of filter-base.js logic.</li><li><b>search/search-space.js</b> is empty.</li><li><b>New file</b>: `alpine-stores.js` - Alpine.js stores for glass state and darkMode.</li><li><b>New file</b>: `player/page-file-manager.js` - file manager actions module.</li><li><b>New file</b>: `player/simple-datatables.js` - bundled DataTables library (~6993 lines).</li><li><b>Global window assignments</b> - many functions on `window` (webHookMapTile, emitFanout, closeGlass, theEditor).\n10. <b>One TASK found</b> in rc.jq.html (empty execState function)\n11. <b>#e-main / #e-secondary</b> in the nav bar are context-sensitive: their text and href change per layer. When in paper, main links to space and secondary links to edit. When in edit, secondary becomes \"save\".\n12. <b>Glass is a toggle overlay</b>, not a separate \"page\". `closeGlass()` / `goToPrev(STATE)` navigates back to the last non-glass layer. The `@lastGlassState` flag controls whether glass auto-opens on boot.</li></ol>\n\n<hr/>\n\n<h2>Analysis: PROXY vs proxy</h2>\n\n<h3>Intended pattern</h3>\n\n<ul><li>`PROXY` (uppercase, line 41) — module-level singleton, the global dependency bag</li><li>`proxy` (lowercase) — local builder that accumulates props before merging into `PROXY`</li></ul>\n\n<h3>What actually happens</h3>\n\n<p>The builder pattern doesn't exist in the code. `proxy` is never written to — it's always a reader.</p>\n\n<p><b>Merge point</b> (line 3288): `Object.assign(PROXY, { mapShow, notyf, repoName, ... })` fills the singleton.</p>\n\n<p><b>Call site</b> (line 3505): `addOneTimeTweaksTodo({ ...PROXY, ssss, baseUrl, crudURL, STATE })` creates a copy of PROXY with extra props, passed as `proxy`. No function ever writes back to it.</p>\n\n<h3>Violations — proxy used as reader</h3>\n\n<p>| Function                                 | Line | Reads from `proxy`                                         | Writes? |\n| ---------------------------------------- | ---- | ---------------------------------------------------------- | ------- |\n| `reactArmSelection(proxy)`               | 306  | `notyf`                                                    | No      |\n| `addOneTimeTweaksTodo(proxy)`            | 333  | `ssss`, `baseUrl`, `crudURL`, `emitFanoutMessage`, `notyf` | No      |\n| `initGlassAutocompleteInput(..., proxy)` | 570  | `notyf`                                                    | No      |\n| `initGlassMenu(..., proxy)`              | 846  | nothing — passes `proxy` down                              | No      |</p>\n\n<h3>Consequence</h3>\n\n<p>`proxy` is really \"PROXY + a few extras\" — a snapshot copy, never a builder. The lowercase/uppercase distinction suggests a pattern that isn't followed. All uses are reads.</p>\n\n<h3>Fix applied</h3>\n\n<p>Enforced the pattern: `PROXY` is the singleton, functions read from it directly.</p>\n\n<p><b>Changes in player.js:</b></p>\n\n<ul><li>Added `ssss`, `STATE`, `baseUrl`, `repoUrlBase`, `crudURL`, `addResultItems` to `Object.assign(PROXY, ...)` at the assembly site</li><li>Removed `proxy` parameter from: `reactArmSelection()`, `addOneTimeTweaksTodo()`, `initGlassAutocompleteInput()`, `initGlassMenu()`</li><li>All internal functions now read from module-level `PROXY`</li><li>Removed unused `PROXY` shadow param from `savePaper()`</li><li>Removed `PROXY` arg from `detectActor()` calls (was unused in larry.js)</li><li>`addOneTimeTweaksTodo()` call simplified from `addOneTimeTweaksTodo({...PROXY, ssss, ...})` to `addOneTimeTweaksTodo()`</li></ul>\n\n<p><b>Changes in external modules:</b></p>\n\n<ul><li>`larry.js`: removed unused `PROXY` param from `detectActor()`</li><li>`search/search-repo.js`: removed unused `proxy` param from `searchInRepoJson()`</li><li>`glass-search.js`: removed `proxy.addResultItems = addResultItems` write (now on PROXY at assembly). Removed `proxy` from `searchInRepoJson()` call</li><li>`glass-search.js` + `search/search-fragments.js`: still receive `proxy` (actually PROXY) as param — these are external modules that can't access the module-level const</li></ul>\n\n<hr/>\n\n<h2>Refactoring: boot() + actions.js</h2>\n\n<h3>What changed</h3>\n\n<p>Eliminated boot code duplication across view.jq.html, rc.jq.html, and home.jq.html. Before: each page repeated ~50 lines of config parsing, `initRepolero()`, `initMqStomp()`, `player()` call, and `window.*` action definitions. After: each page calls `boot(ssss, opts)`.</p>\n\n<h3>New files</h3>\n\n<p><b>actions.js</b> — shared `window.*` function registrations:</p>\n\n<p>```</p>\n\n<p>registerUploadActions({ crudURL, webhookUrlBase, uploadFileWithWebhook })\n-> window.uploadCrud\n-> window.uploadHook</p>\n\n<p>registerRepoActions({ ssss, stompClient, repoName, webhookUrlBase })\n-> window.emitRepoIndex (REPO_AFFERENT)\n-> window.emitRepoSync (REPO_AFFERENT)\n-> window.emitRepoActualize (REPO_AFFERENT)\n-> window.emitRepoMail (REPO_SERIAL)\n-> window.webHookRepoStatus (webhook)</p>\n\n<p>```</p>\n\n<h3>boot() in player.js</h3>\n\n<p>```</p>\n\n<p>export async function boot(ssss, opts = {})</p>\n\n<ol><li>Read config from <script id=\"app-config\"> JSON block</li><li>initRepolero(ssss, repoUrlBase, env)</li><li>Auto-set #\u0001\u001drepoName text if element exists</li><li>initMqStomp (skipped if opts.noMq or no StompJs global)</li><li>registerUploadActions + registerRepoActions (if mqBridge)</li><li>player({...config, ...opts, fileManager}) (skipped if opts.noPlayer)</li><li>opts.onBoot(result) callback for page-specific setup\n   returns { repoName, repoUrl, baseUrl, dataUrlBase, mqBridge, ssss, config }</li></ol>\n\n<p>```</p>\n\n<p><b>opts:</b>\n| Key | Type | Effect |\n|---|---|---|\n| `mapShow` | function | OpenLayers map module (passed to player) |\n| `dashboard` | function | Dashboard module (passed to player) |\n| `fileManager` | function | File manager module (passed to player) |\n| `cm6` | object | CodeMirror 6 (falls back to `window.cm6`) |\n| `openLayers` | object | OpenLayers lib (passed to player) |\n| `noMq` | bool | Skip MQ/STOMP init |\n| `noPlayer` | bool | Skip player() call entirely |\n| `onBoot` | function | Callback after boot, receives result object |</p>\n\n<h3>Config injection: `<script id=\"app-config\">`</h3>\n\n<p>Each page has one jq expression that produces all config as JSON:</p>\n\n<p>```html\n<script id=\"app-config\" type=\"application/json\">\n  ¦{env: .site.env, siteVer: .site.ver, siteHotkey: .site.hotkey,\n    assetsURL: (\"assets://icons/\"|s4url), mqBrokerURL: (\"mq:/ws\"|s4url),\n    dbSqliteURL: (\"sqlite:/\"|s4url), remoteStateURL: (\"state:/\"|s4url),\n    crudURL: (\"crud://\"|s4url), webhookUrlBase: (\"webhook:/hooks\"|s4url),\n    repoUrlBase: (\"repo:/\"|s4url), protocolRouter: (.protocolRouter),\n    pages: (.site.menu.pages|keys)} | tojson¦\n</script>\n```</p>\n\n<p>Note: `.protocolRouter` is passed through as-is (already a JSON string from the jq context). `boot()` handles parsing it.</p>\n\n<h3>Result per page</h3>\n\n<p><b>view.jq.html</b> — full app, all layers (Alpine.js via CDN):</p>\n\n<p>```html\n<script src=\"./alpine-stores.js\"></script>\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss);\n</script>\n```</p>\n\n<p><b>rc.jq.html</b> — paper-only, MQ logging, D3 MSC chart:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  const { repoName, mqBridge, config } = await boot(ssss, {\n    onBoot({ repoName, mqBridge, ssss, config }) {\n      // rc: MQ fanout logging\n      // rc: override webHookRepoStatus with processOutput\n    },\n  });\n  // rc-specific: execSqlite, D3 MSC chart (stays inline)\n</script>\n```</p>\n\n<p><b>home.jq.html</b> — landing page, no player, no MQ:</p>\n\n<p>```html\n<script type=\"module\" async>\n  import ssss from \"...\";\n  import { boot } from \"./player.js\";</p>\n\n<p>  boot(ssss, { noMq: true, noPlayer: true });\n</script>\n```</p>\n\n<h3>TODO: Production optimizations (Brotli + Minify)</h3>\n\n<p>The dev page is served by Caddy from `dist/`. Caddyfile: `/home/bebo/dev/k2/untiloop/linode-server/caddy/local/Caddyfile.local.caddyfile`.</p>\n\n<p><b>Brotli compression</b> — Currently uses `encode gzip zstd` (line 8). Add `br`: `encode br gzip zstd`. One-word change, ~15-20% smaller than gzip alone.</p>\n\n<p><b>JS minification</b> — ~40-60% size reduction before compression. Run only for production (minified JS is hard to debug). Minify all JS in-place:</p>\n\n<p>```sh</p><h1>install esbuild (once)</h1><p>npm i -g esbuild</p>\n\n<h1>minify all .js files in current folder and below</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\n```</p>\n\n<p><b>Combined</b> (minify + pre-compress for Caddy static serving):</p>\n\n<p>```sh</p><h1>minify + generate .br files</h1><p>find . -name '*.js' -exec esbuild --minify --outfile={} --allow-overwrite {} \\;\nfind . -name '*.js' -exec brotli -f {} \\;</p>\n\n<h1>then in Caddyfile, use:</h1><h1>file_server { precompressed br gzip }</h1><p>```</p>\n\n<p>Note: these commands modify files in-place. Run on `dist/` copies, not on `source/`.</p>\n\n<h1>Split player.js into modules</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player-utils.js` (~265 lines) — constants, route/URI helpers, hotkeys</li><li>[x] `player-webhook.js` (~145 lines) — isWebHookOn, upload, editSave, savePaper</li><li>[x] `player-state.js` (~43 lines) — resetState, resetMachine</li><li>[x] `player-mq.js` (~147 lines) — mqFanoutConsumer, emitFanout/Mq/Tile</li><li>[x] `player-glass.js` (~340 lines) — initGlassAutocompleteInput, initGlassMenu, initGlassStatus</li><li>[x] `player-tweaks.js` (~247 lines) — reactArmSelection, addOneTimeTweaksTodo, observeSubTree</li><li>[x] `player-paper.js` (~426 lines) — eSetOnOff, createFilterPapers, renderPaper</li><li>[x] `player-edit.js` (~233 lines) — renderSave, renderEdit</li><li>[x] `player-space.js` (~253 lines) — resetSpace, renderSpace</li><li>[x] `player.js` rewritten as slim orchestrator (3672 → 1022 lines)<ul><li>imports from all 9 modules</li><li>keeps: boot(), player() shell, notyf setup, PROXY assembly</li><li>renderLayerMode switch skeleton calls: renderPaper, renderEdit, renderSave, renderSpace</li><li>bootS4 callback: glass init, mq subscribe, persistent elements, window.* hooks</li><li>dropped dead code (keyNav), commented-out blocks, duplicate functions</li><li>reloadCurrentPaper uses ref pattern (ref.fn) for MQ reload</li><li>player-mq.js updated to use PROXY.reloadCurrentPaper.fn()</li></ul></li></ul>\n\n<h1>Touchbar & Double-Touch</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] Alpine.js replaced Litedom for touchbar</li><li>[x] Double-touch changed from 2-finger simultaneous to <b>double-tap</b> pattern (400ms timeout)</li><li>[x] Native click delegation instead of Alpine @click (avoids double-fire)</li><li>[x] Toolbar buttons: click = primary action, double-touch = `action_extra`</li><li>[x] Mobile paste: uses native `prompt()` (no nice dialog, but works)</li><li>[x] Fragment touchbar: added `touchstart` binding for `$`-prefixed tooltips</li><li>[x] `data-doubletouch` only rendered when `action_extra` exists</li><li>[x] Fixed `window.touchbarDispatch` to receive origin element (not CustomEvent)</li><li>[x] Mobile hide bug: `touchstart` hide path now sets `toolbarJustShown` so the synthetic `click` ~300ms later cannot re-show the toolbar (ui-touchbar.js:672)</li><li>[x] Auto-close on action click: `window.touchbarDispatch` calls `fnHide()` after the switch so any toolbar button click closes the touchbar</li><li>[x] Per-def `hideAfter` opt-in debounce in `ui-touchbar-defs.js`:<ul><li>omitted / `false` / non-numeric (default) → <b>keep open</b> (touchbar stays visible after action; user dismisses via re-tap on trigger or via `glass:*` explicit-hide actions)</li><li>`hideAfter: <ms>` (number or numeric string) → schedule `fnHide` N ms later; <b>global last-click-wins</b> — every dispatched click clears the shared module-level `hideTimer` first, so rapid clicks (e.g. multi-vote) reset the countdown</li><li>Wired by emitting `data-hide-after=\"...\"` on each button in `updateToolbarItems` and reading `element.getAttribute(\"data-hide-after\")` in `touchbarDispatch`</li></ul></li><li>[x] <b>`window.touchbarShow(definition, position)`</b> — programmatic touchbar invocation without a `[data-tooltip]` host element. Creates a synthetic element with `id` + `data-tooltip`, calls `tooltipMaker.render` directly, positions via `positionTooltip` with a 0×0 emitter rect at the given viewport coords. Used by `cmdVotingCampaign.js`'s Vega `clickedWord` listener to open the fragment-action menu at the clicked task's position.</li></ul>\n\n<h2>Known gaps</h2>\n\n<ul><li>No \"tap outside to hide\" — toolbar only closes via same-emitter re-tap or specific `touchbarDispatch` verbs (glass:clear, glass:clear-history, glass:history)</li><li>`initTouchpad(\"#alpine-touchpad\", ...)` in player-tweaks.js:147 uses a selector that doesn't exist in HTML (HTML has `#alpine-touchbar`); `initDoubleTouch` still runs because it fires before the selector check</li></ul>\n\n<h1>Tabler</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/tabler.js` now post-renders HTML tables via Tabulator 6.4 (tableHeatmap → new Tabulator)</li><li>[x] `div.tabulator-tableholder` auto-shrinks for short tables: requires <b>both</b> `height: false` and `maxHeight: '90vh'` at tabler.js:150 — `maxHeight` alone did not shrink (Tabulator 6 defaults to fill-parent; `height: false` turns on auto-sizing, `maxHeight` caps it)</li><li>[x] HTML in cells rendered (not escaped): added `columnDefaults: { formatter: 'html' }` — Tabulator default formatter is `plaintext` which escapes markup</li><li>[x] Themed Tabulator CSS in `source/assets/theme-bare/repolero.less` (appended at bottom) — uses the repolero CSS vars (`--background-color-<i>`, `--font-color-</i>`, `--border-color-*`, `--font-family-small`, `--font-family-bold`) so dark/light follows `body.dark` / `body.light` automatically; small mode-specific tweaks for selected-row color and border contrast</li><li>[x] `.tabulator-col` overrides need to be nested under `.tabulator-header` (not just `.tabulator`) to beat the bundled-theme default `.tabulator .tabulator-header .tabulator-col { background:#fff }` — same specificity (0,3,0) wins by cascade order</li></ul>\n\n<h1>Eisenhower matrix</h1>\n\n<h2>Done</h2>\n\n<ul><li>[x] `player/eisenhover-matrix.js` — replaced Vega `wordcloud` transform with a JS-side layout: sort by score desc → linear score→fontSize map → row-flow within quadrant box → vertical-center the row stack → pass pre-positioned `{task, fontSize, x, y}` to a plain Vega text mark. Reason: Vega 5.21 wordcloud transform's per-datum `fontSize` accessor (both `{field}` and `{expr}` forms) throws `Cannot read properties of undefined (reading 'score')`; also wordcloud doesn't pack ~10 short labels deterministically. Bypassing it removes both problems.</li><li>[x] <b>Conflict lines</b> (`config.connect`) — same heading across quadrants is connected by dashed magenta rules. Grouping is keyed by stable `url` (falling back to `task`); the display `task` carries a per-quadrant vote count like `\"filmy (3)\"` and would never match across quadrants. All-pairs interconnect inside each url-group.</li><li>[x] <b>Font family on Vega text marks</b> — `font: { value: fontFamily }` (Vega encode requires `{value:…}` wrapping; bare `font: fontFamily` is silently ignored). Applied to both quadrant labels and the per-task text mark.</li><li>[x] <b>`gravityToCorner` mode</b> (top-of-file toggle, default `true`) — per quadrant, highest-score task gravitates to the matrix's outer corner; lowest-score drifts toward the matrix center. `layoutTasksInBox` always anchors top-left, then mirrors x/y based on quadrant corner (`do=tl, schedule=tr, delegate=bl, eliminate=br`). When `false`, layout reverts to the original centered behaviour.</li><li>[x] <b>Quasi-square grid in gravity mode</b> — `maxPerRow = ceil(sqrt(N))` forces a grid so low-score items don't all sit on one Y line (six items → 3×2 instead of 6×1); vertical stretching can then fire.</li><li>[x] <b>Stretch to span box</b> — with >1 row, distribute extra vertical gap so the last row reaches `boxH`; same per row for items reaching `boxW`. Highest stays pinned at the outer corner, lowest lands at the matrix center.</li><li>[x] <b>Label clearance</b> — `padBottom: 8 → 30` (matches `titleSpace`); `delegate`/`eliminate` labels sit at the bottom edge and no longer collide with tasks.</li><li>[x] <b>2 px quadrant gap</b> — new `quadrantGap = 2` (`half = 1`): rects shrink 1 px on inner edges, right-column / bottom-row offsets shift by `half`, `boxW`/`boxH` shrink by `half` so tasks stay inside their panel.</li><li>[x] <b>`harmonizeColors` helper</b> — hex→HSL→hex; keeps each color's hue but equalises saturation/lightness (default `{ s: 0.55, l: 0.78 }`) so the four quadrant fills feel like one palette family. Exported `quadrantColors` stays as the raw Dracula palette; harmonised set is computed locally inside `tasksToEisenhoverMatrix`.</li><li>[x] <b>Density culling fontSize re-evaluation</b> — after density culling drops lowest-scoring tasks to meet `densityOptimum`, `fontSizeRange` is now recalculated from the reduced task count (`laid.length / (boxW * boxH)`) before the re-layout. Previously the original pre-culling font sizes were reused, leaving remaining tasks smaller than they could be.</li><li>[x] <b>`showBorder` box width fix</b> — Two corrections:<ul><li><b>First pass (all layout engines):</b> `width` was `estimateTextWidth(t.task + \", \" + (t.miniLabel || \"\"), ...)` — the trailing `\", \"` inflated the box by ~2 chars when `miniLabel` was empty. Changed to `estimateTextWidth(t.miniLabel ? t.task + \", \" + t.miniLabel : t.task, ...)`.</li><li><b>Second pass (eisenhover-matrix.js positioning loop):</b> The border rect was using `width` (`estimateTextWidth(task + miniLabel + separator, taskFont, taskWeight)`), but the miniLabel is a <b>separate Vega text mark</b> at fontSize 10, positioned right of the main task. The box should only cover the main task label. Changed `width: t.width || 0` → `width: t.taskWidth || t.width || 0` so the rect matches the task-only width. `taskWidth` is `estimateTextWidth(task, fontSize, fontWeight)` without any miniLabel content — set by all active layout engines (boss5, boss4, boss3, boss2, normal).</li></ul></li><li>[x] <b>StdDev-based density culling</b> — Replaced simple ascending-score drop with two-pass culling per quadrant:<ol><li>Compute `mean` and `stdDev` of task scores in quadrant</li><li><b>First pass:</b> Drop statistical outliers (score < mean − 1×stdDev) until density ≤ `densityOptimum`</li><li><b>Second pass:</b> If still over threshold, drop lowest remaining non-outliers</li><li>Recalculate `fontSizeRange` on reduced density (fewer tasks → bigger font) and re-layout via `runLayout()`</li><li>Controlled by `config.densityOptimum` (default `0.6`)</li><li>`_densityCulled` boolean on returned spec object; captured by `cmdVotingCampaign.js` to append `[culled]` to status bar</li></ol></li><li>[x] <b>Font scale cap now width-proportional</b> — Replaced hardcoded `2.0` (and the earlier `isMobile ? 1.3 : 2.0`) with `boxW / 180` in the `Math.min()` chain. Also fixed the same stale `2.0` in the re-layout after density culling. This adapts naturally to any viewport: `boxW=194` (mobile 390×844) → cap ~1.08, `boxW=400+` (desktop) → cap 2.0 (unchanged). No explicit `isMobile` check needed. Both initial layout and culling re-layout use the same formula.</li></ul>\n\n<h2>Use</h2>\n\n<ul><li>`player/eisenhover-matrix.js` exports `tasksToEisenhoverMatrix(ssss, tasks, config)` and `quadrantColors`.</li><li>Called from:<ul><li>`commands/cmdDashboard.js` — dashboard \"matrix\" image, votingTypes `{do:hot, schedule:pin, delegate:weak, eliminate:veto}`.</li><li>`commands/cmdVotingCampaign.js` — live voting campaign; the Vega `clickedWord` signal handler calls `window.touchbarShow` at the clicked task's position to open the per-vote action menu.</li></ul></li></ul>\n\n<h2>Touchbar Actions</h2>\n\n<p>| Button                    | Click                  | Double-touch   |\n| ------------------------- | ---------------------- | -------------- |\n| duplicate-svgrepo-com.svg | `editor:duplicateLine` | `editor:paste` |\n| (other buttons)           | primary action         | `action_extra` |</p>\n\n<h2>Files Changed</h2>\n\n<ul><li>`ui-touchbar.js`: Alpine init, native click delegation, double-tap pattern</li><li>`ui-touchbar-defs.js`: `paste` button with `action`/`action_extra`</li><li>`player/player-tweaks.js`: pass `origin` element to `touchbarDispatch`</li><li>`codeditor/src/databox.js`: mobile paste detection</li></ul>\n\n<hr/>\n\n<h2>Testing</h2>\n\n<p>```sh</p><h1>Unit/snapshot tests (Jest + ESM)</h1><p>npm test</p>\n\n<h1>E2E (Playwright)</h1><p>cd e2e\nnpm test                 # headless\nnpm run test:headed      # headed\nnpm run test:ui          # ui mode\n```</p>\n\n<p>Jest requires `--experimental-vm-modules` (already in `package.json` `test` script).\nSnapshot file: `source/repolero-prototype/test/glass-search.test.mjs`.\nSnapshot dir: `source/repolero-prototype/test/<b>snapshots</b>/`.</p>\n\n<h2>Key conventions</h2>\n\n<ul><li><b>ES modules</b> (`\"type\": \"module\"`) — use `import`/`export`, no CommonJS</li><li><b>No TypeScript</b> — pure JS with JSDoc</li><li><b>Prettier</b> (`npx prettier`) with `semi: false` (except `.test.js` files)</li><li><b>No linter or typechecker</b> configured</li></ul>\n\n<h2>Voting system</h2>\n\n<ul><li>SQLite via HTTP (`sqlite-http` crate, `remote-sqlite.js`)</li><li>`STATE.remoteSqlite.execSql(sql)` — uses <b>string interpolation</b> in SQL (NOT `?` placeholders)<ul><li>Reference: `cmdVotingCampaign.js:90`, `player-paper.js:1020`</li><li>Do NOT use parameterized `?` placeholders with args array — the `sqlite-http` version doesn't support them</li></ul></li><li>Table: `repo_voting` (columns: `d`, `user`, `repo`, `campaign`, `ref`, `operation`, `num1`, `num2`)</li><li>`fragmentRef` format must match `cache_ast_lake_nodes_ref.ref`: relative path like `paper/filename.mdd:lineNum::sanitized/trail/path`<ul><li><b>Do NOT</b> use full filesystem paths (e.g., `/home/looper/.../paper/...`)</li><li><b>Do</b> strip `#/paper/` prefix from URIs</li><li><b>Do</b> sanitize trail segments via `ssss.string.sanitizeName`</li></ul></li><li><b>Campaign prefix</b>: `<select id=\"activeCampaign\" persistent>` in `view.jq.html:368` holds the active campaign scope (e.g. `eisenhower`, `debate`). Read via:\n  ```js\n  window.store(windowStoreKey(\"options/glass/activeCampaign\"));\n  ```\n  The `[persistent]` handler in `player.js:1000-1046` auto-saves/restores `SELECT` values to localStorage. A sync to `windowStoreKey(\"options/glass/activeCampaign\")` is added for `#activeCampaign` specifically.</li><li><b>Vote helper</b>: `voteRepo(STATE, campaign, ref, operation)` in `player-utils.js:643` — shared INSERT wrapper used by all call sites. Prepends the activeCampaign prefix to the campaign value.</li><li><b>Fragment ref helper</b>: `buildFragmentRef(basePath, trail, sanitizeName)` in `player-utils.js:635` — constructs `path::sanitized/trail` from a path (strips existing `::`), accepting trail as `;`-delimited string or array. Used in both paper layer (`player-utils.js:577`) and glass layer (`player-glass.js:86`).</li><li><b>JSON options loader</b>: `loadJsonOptions(url, storeKey)` in `player-utils.js:643` — fetches JSON from `url` and stores on `Alpine.store(\"glass\")[storeKey]`. Used for dynamic campaign options from `campaignsJsonUrl`. General-purpose for any JSON-loaded options.</li><li><b>Dialog-campaignes</b>: `<dialog id=\"dialog-campaignes\">` in `view.jq.html:370` populated dynamically from `Alpine.store(\"glass\").campaigns`. On close, stores selection to `window.store(windowStoreKey(\"options/glass/activeCampaign\"))`. Wiring in `player.js:1052-1075`.</li><li><b>After voting</b>, dispatch `document.dispatchEvent(new Event(\"paper:reload\"))` to refresh the dashboard (listener in `player.js:989`). Use `echoOut({vote: {...}})` to broadcast to other clients via STOMP fanout.</li></ul>\n\n<h2>Touchbar system API</h2>\n\n<ul><li>`window.touchbarShow(\"$definition|params\", {x, y})` — programmatic show</li><li>`window.touchbarDispatch(action, element)` — button click dispatch</li><li>`data-touchpad=\"type|params\"` — attribute on glass search `<li>` for double-tap</li><li>`data-tooltip` — attribute for click/touch triggered toolbar</li><li>Button defs in `toolbarsDefs` (`ui-touchbar-defs.js`)</li><li>`$resultAction`, `$fragmentAction`, `$statusTLToolbar` are the active toolbar types</li><li><b>resultAction dispatch</b> (`ui-touchbar.js:822`): inline handlers for `copyRef` and `vote-cast/uncast`. Vote handler calls `voteRepo(proxy.STATE, ...)`, shows notyf, dispatches `\"paper:reload\"`, hides toolbar via `.finally(() => fnHide())`.</li></ul>\n\n<h2>Architecture quirks</h2>\n\n<ul><li>`window.ctrlPaper` is only set inside `renderPaper()` — NOT available on the glass layer. Result action dispatch handlers in `touchbarDispatch` must be self-contained (use `proxy.STATE.remoteSqlite` and `proxy.ssss.copyText` directly).</li><li>`proxy.STATE` is the mutable global state object (passed by reference).</li><li>Alpine store at `Alpine.store(\"glass\")` — used for touchbar state and menu state.</li><li>Search results in `onResults` callback have fields: `reducedName, name, uri, fn, size, mt, ts, scaledTs, scaledSize, type, trail, cssClass`. The `fn` field is the raw DB identifier (full path with line number) — use `uri` (hash URL) for relative paths instead.</li><li>Voting dashboard reads from `repo_voting` via `INNER JOIN cache_ast_lake_nodes_ref r USING(id)` — if the `ref` doesn't match, the vote row is invisible.</li><li>`[persistent]` attribute on elements (`player.js:1000-1046`): auto-saves/restores values via `window.store(windowStoreKey(ssss.getNodeTrail(element)))`. Supports INPUT (text/checkbox), SELECT, and DETAILS elements. Fires on `sEventGlassReady`.</li><li><b>`echoOut`</b> (`player-paper.js:366`): emits fanout messages via `PROXY.emitFanoutMessage({verb: \"paper:echo\", ...})`. Used to broadcast vote events to other connected clients. Available only inside `renderPaper()` and `dashboardBody()` (from `proxy.echoOut`).</li></ul>\n\n<h2>Heading-changes / Fragment management</h2>\n\n<h3>Files changed</h3>\n\n<ul><li>`player/player-utils.js` — new functions: `validateFragmentUrl`, `loadFragment`, `hasMetaData`, `buildFragmentRef`, `loadJsonOptions`</li><li>`player/player-paper.js` — new touchbar action `replantRef` in `window.ctrlPaper`; `paperHeadingReport` wired with `hasMetaData` filter</li><li>`proc-paper-edit.mjs` (server-side webhook) — vote deletion logic confirmed: `resolvedRenames` → UPDATE votes, `confirmedDeletions` → DELETE votes, skipped items left untouched</li></ul>\n\n<h3>Dialog rename</h3>\n\n<p>`broken-refs-dialog.js` → `broken-refs-managment.js`. Import updated in `player.js` and `portlets.md`.</p>\n\n<h3>hasMetaData filter (heading report)</h3>\n\n<p>`hasMetaData(PROXY, repoName, refs)` in `player-utils.js` — queries `repo_voting` for refs with existing votes, returns a `Set`. Wired into `paperHeadingReport` to filter `allEvents` so only candidates with votes appear in the report/fanout.</p>\n\n<p>`[preselect]` focus on dialog open: `headingDialog.querySelector(\"[preselect]\")?.focus()` after `showModal()`.</p>\n\n<p>Delete-option icon: `si-cross` → `si-trash` in `broken-refs-managment.js`.</p>\n\n<h3>fragmentUrl system</h3>\n\n<p>Format: `<relative-file-path.ext>::<sanitized-breadcrumb/segments>` (e.g. `paper/scratchpad.mdd::candidateDruhy`).</p>\n\n<p><b>Functions:</b></p>\n\n<ul><li>`validateFragmentUrl(s)` — validates `path.ext::seg1/seg2` (exactly one `::`, file extension, non-empty segments)</li><li>`loadFragment(fragmentUrl, STATE, ssss, deep, repoUrlBase)`:<ol><li>Validates fragment URL format</li><li>Queries `cache_ast_lake_nodes` via `cache_ast_lake_nodes_ref` for the node by `r.ref = fragmentUrl` — fetches `n.id, r.ref, n.body, n.num1, n.num2` (num2 is heading level 1–6)</li><li>Extracts `id` (full filesystem path + line number) and `num1` (line number fallback)</li><li>Derives `pathInRepo` by splitting on `/repo/<name>/` and stripping line suffix</li><li>Determines range end:<ul><li>`deep=false`: SQL query finds the very next heading in the file — returns just the heading's direct content</li><li>`deep=true`: scans file content from heading line forward, finds the next heading at same or higher level (parses `#` markers directly from text) — includes all nested children; `endLine` reflects the full subtree boundary</li></ul></li><li>Fetches file content via `ssss.fetchWithETag(fetchUrl, { token: _getToken() })` where fetchUrl is `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`</li><li>Slices `lines[start..end]` (start = lineNum - 1)</li><li>Returns `{ text, pathInRepo, startLine, endLine }` or `null`</li></ol></li></ul>\n\n<ul><li>`relevelFragment(text, cursorLevel)` — pure: shifts all heading levels in fragment text so its root heading becomes `cursorLevel + 1`. Clamps to 1–6. Returns text unchanged if no headings found or shift is zero.</li></ul>\n\n<h3>replantRef touchbar action</h3>\n\n<p>Triggered via touchbar `paper:replantRef` in `window.ctrlPaper`. Flow:</p>\n\n<ol><li>Reads clipboard (currently hardcoded test value `\"paper/scratchpad.mdd::candidateDruhy\"`)</li><li>Checks `route.isInEditor` — only works in edit layer</li><li>Validates via `validateFragmentUrl(t)`</li><li>Loads fragment via `loadFragment(t, STATE, ssss, true, PROXY.repoUrlBase)`</li><li>Determines heading level at cursor via `getCursorHeadingLevel(theEditor)` — scans backwards from cursor for nearest `#` heading line</li><li>Applies `relevelFragment(fragment.text, cursorLevel)` to shift the fragment's heading levels under the cursor</li><li>Inserts adjusted text into CM6 editor via `window.theEditor.panes.one.view.dispatch({ changes: { from: view.state.selection.main.head, insert: adjusted } })`</li></ol>\n\n<p>Key: `window.theEditor` is the `initEditor()` return object. Raw CM6 `EditorView` is at `window.theEditor.panes.one.view`.</p>\n\n<h3>URL construction</h3>\n\n<p>Following `tokensToPaths` pattern: `ssss.Url(\\`${repoUrlBase}/${repoName}/${pathInRepo}\\`)`. Data server (e.g. `repo.untiloop.loc`) serves files at `/<repoName>/<path>` — separate from app server (`repolero.untiloop.loc`).</p>\n\n<h3>SQL rows</h3>\n\n<p>`STATE.remoteSqlite.execSql(sql)` returns `{ rows: [[col1, col2, ...], ...] }`. Destructure by index, not object destructure.</p>\n\n<h2>OuchDB (Remote CouchDB/PouchDB)</h2>\n\n<p><b>Adapter:</b> `source/assets/s4/adapters/remote-ouchdb.js` — `RemoteOuchDb` class exposed as `ssss.RemoteOuchdb`.</p>\n\n<p><b>Wiring:</b> `dbOuchdbURL` is resolved from `\"ouchdb:/\"|s4url` in each `.jq.html` config block, passed through `player.js`, and instantiated as:</p>\n\n<p>```\nSTATE.remoteOuchdb = new ssss.RemoteOuchdb(dbOuchdbURL + \"/repolero\")\n```</p>\n\n<p>The `\"/repolero\"` suffix is the database name, appended at instantiation (not in the adapter).</p>\n\n<p><b>Protocol config:</b> `s4.config.json` protocolRouter maps `ouchdb:/?(env:loc)` and `ouchdb:/?(env:linode)` → `https://ouchdb.untiloop.website`.</p>\n\n<p><b>Usage (vegaLinks persistence):</b></p>\n\n<ul><li>`loadVegaLinks()` — GETs doc from OuchDB on dashboard init, populates `vegaLinks` array</li><li>`saveVegaLinks()` — calls `STATE.remoteOuchdb.update()` after push/splice on `vegaLinks`</li><li>Guard: `if (!STATE.remoteOuchdb) return`</li><li>Doc ID pattern: `{repoName}/campaign/{campaignId}` (CouchDB slash-separated ID)</li></ul>\n\n<p><b>Adapter API:</b> `get(docId)`, `put(docId, document)`, `delete(docId, rev)`, `allDocs(options)`, `update(docId, document)` — reads `_rev` then PUTs (create or update).</p>\n\n<p><b>`update()` method:</b> GETs existing doc for `_rev`, catches 404 silently for fresh create, then PUTs. Uses `this.request()` (not `this.get()`) to avoid noisy `console.error` on expected 404.</p>\n\n<h3>CORS conflict: Caddy + PouchDB double headers</h3>\n\n<p>`pouchdb-server` has CORS enabled by default (`httpd.enable_cors: true`, `cors.credentials: true` in `lib/cors.js`). When proxied through Caddy, the Caddyfile also adds CORS headers — resulting in DUPLICATE response headers:</p>\n\n<p>```\nAccess-Control-Allow-Origin: *                       ← from Caddy (Caddyfile lines 405-408)\nAccess-Control-Allow-Origin: http://repolero.untiloop.loc  ← from PouchDB (corser echoes Origin)\nAccess-Control-Allow-Credentials: true                ← from PouchDB (cors.credentials default)\n```</p>\n\n<p>Chrome reads the <b>first</b> `Access-Control-Allow-Origin` (`<i>`), sees `Access-Control-Allow-Credentials: true`, and rejects — the Fetch spec forbids `</i>` with credentials.</p>\n\n<p><b>Fix:</b> Disable CORS in `pouchdb-server` since Caddy already handles it. Config file at `linode-server/caddy/linode/pouchdb-cors-off.json`:</p>\n\n<p>```json\n{ \"httpd\": { \"enable_cors\": false } }\n```</p>\n\n<p>Start with `--config` flag:</p>\n\n<p>```\nnpx pouchdb-server --port 5984 --dir $dirBase/tmp.ouchdb --config $dirBase/pouchdb-cors-off.json\n```</p>\n\n<h2>eisenhover-matrix.js — Vega spec builder</h2>\n\n<p><b>Location:</b> `source/repolero-prototype/player/eisenhover-matrix.js`</p>\n\n<p><b>Entrypoint:</b> `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object.</p>\n\n<p><b>Config options:</b></p>\n\n<p>| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| `width`               | number  | 600     | Spec width                                                                             |\n| `height`              | number  | 500     | Spec height                                                                            |\n| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |\n| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |</p>\n\n<p><b>Path utilities:</b></p>\n\n<ul><li>`taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering).</li><li>`taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point.</li></ul>\n\n<p><b>Conflict edge rendering:</b> Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes.</p>\n\n<h2>Traps pattern (cleanup on route leave)</h2>\n\n<p>`commands.js` provides a <b>traps</b> mechanism to revert side effects when a `@command` route is left:</p>\n\n<ul><li>`getCommandHandler()` creates a `traps = []` array per invocation</li><li>Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`</li><li>Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`</li><li>On route change, `cleanup()` iterates through all traps and calls each one `await`</li><li>The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`</li></ul>\n\n<p><b>Current trap users:</b></p>\n\n<p>| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |\n| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/<i> TODO traps signalId </i>/}` — placeholder                                                              |\n| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |\n| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |\n| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |</p>\n\n<p><b>When to use:</b> Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages.</p>\n\n<p><b>Caveat — don't capture inline style to restore:</b> The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. <b>Always restore to `\"\"`</b> (remove inline style → CSS cascade takes over), never capture `style.*` for later restore.</p>\n\n<h2>cmdVotingCampaign.js — link persistence</h2>"},{"id":"/root/children/224","type":"heading","loc":{"start":65598,"end":65641,"line":{"s":1082,"e":1082,"code":["## eisenhover-matrix.js — Vega spec builder"]},"column":{"s":0,"e":43}},"dim":["","heading.224"],"code":"## eisenhover-matrix.js — Vega spec builder","symbName":"heading","symbRange":[65643,67759],"symbRangeL":[1082,1107],"outerCode":"\n**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`\n\n**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object.\n\n**Config options:**\n\n| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| `width`               | number  | 600     | Spec width                                                                             |\n| `height`              | number  | 500     | Spec height                                                                            |\n| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |\n| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |\n\n**Path utilities:**\n\n- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering).\n- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point.\n\n**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes.","outerHtml":"\n<p><b>Location:</b> `source/repolero-prototype/player/eisenhover-matrix.js`</p>\n\n<p><b>Entrypoint:</b> `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object.</p>\n\n<p><b>Config options:</b></p>\n\n<p>| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| `width`               | number  | 600     | Spec width                                                                             |\n| `height`              | number  | 500     | Spec height                                                                            |\n| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |\n| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |</p>\n\n<p><b>Path utilities:</b></p>\n\n<ul><li>`taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering).</li><li>`taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point.</li></ul>\n\n<p><b>Conflict edge rendering:</b> Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes.</p>"},{"id":"/root/children/224/children/0","type":"text","loc":{"start":65601,"end":65641,"line":{"s":1082,"e":1082,"code":["## eisenhover-matrix.js — Vega spec builder"]},"column":{"s":3,"e":43}},"dim":["","heading.224","text.0"],"code":"eisenhover-matrix.js — Vega spec builder"},{"id":"/root/children/225","type":"paragraph","loc":{"start":65643,"end":65712,"line":{"s":1084,"e":1084,"code":["**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`"]},"column":{"s":0,"e":69}},"dim":["","paragraph.225"],"code":"**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`"},{"id":"/root/children/225/children/0","type":"strong","loc":{"start":65643,"end":65656,"line":{"s":1084,"e":1084,"code":["**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`"]},"column":{"s":0,"e":13}},"dim":["","paragraph.225","strong.0"],"code":"**Location:**"},{"id":"/root/children/225/children/0/children/0","type":"text","loc":{"start":65645,"end":65654,"line":{"s":1084,"e":1084,"code":["**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`"]},"column":{"s":2,"e":11}},"dim":["","paragraph.225","strong.0","text.0"],"code":"Location:"},{"id":"/root/children/225/children/1","type":"text","loc":{"start":65656,"end":65657,"line":{"s":1084,"e":1084,"code":["**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`"]},"column":{"s":13,"e":14}},"dim":["","paragraph.225","text.1"],"code":" "},{"id":"/root/children/225/children/2","type":"inlineCode","loc":{"start":65657,"end":65712,"line":{"s":1084,"e":1084,"code":["**Location:** `source/repolero-prototype/player/eisenhover-matrix.js`"]},"column":{"s":14,"e":69}},"dim":["","paragraph.225","inlineCode.2"],"code":"`source/repolero-prototype/player/eisenhover-matrix.js`"},{"id":"/root/children/226","type":"paragraph","loc":{"start":65714,"end":65807,"line":{"s":1086,"e":1086,"code":["**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object."]},"column":{"s":0,"e":93}},"dim":["","paragraph.226"],"code":"**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object."},{"id":"/root/children/226/children/0","type":"strong","loc":{"start":65714,"end":65729,"line":{"s":1086,"e":1086,"code":["**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object."]},"column":{"s":0,"e":15}},"dim":["","paragraph.226","strong.0"],"code":"**Entrypoint:**"},{"id":"/root/children/226/children/0/children/0","type":"text","loc":{"start":65716,"end":65727,"line":{"s":1086,"e":1086,"code":["**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object."]},"column":{"s":2,"e":13}},"dim":["","paragraph.226","strong.0","text.0"],"code":"Entrypoint:"},{"id":"/root/children/226/children/1","type":"text","loc":{"start":65729,"end":65730,"line":{"s":1086,"e":1086,"code":["**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object."]},"column":{"s":15,"e":16}},"dim":["","paragraph.226","text.1"],"code":" "},{"id":"/root/children/226/children/2","type":"inlineCode","loc":{"start":65730,"end":65776,"line":{"s":1086,"e":1086,"code":["**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object."]},"column":{"s":16,"e":62}},"dim":["","paragraph.226","inlineCode.2"],"code":"`tasksToEisenhoverMatrix(ssss, tasks, config)`"},{"id":"/root/children/226/children/3","type":"text","loc":{"start":65776,"end":65807,"line":{"s":1086,"e":1086,"code":["**Entrypoint:** `tasksToEisenhoverMatrix(ssss, tasks, config)` returns a Vega v5 spec object."]},"column":{"s":62,"e":93}},"dim":["","paragraph.226","text.3"],"code":" returns a Vega v5 spec object."},{"id":"/root/children/227","type":"paragraph","loc":{"start":65809,"end":65828,"line":{"s":1088,"e":1088,"code":["**Config options:**"]},"column":{"s":0,"e":19}},"dim":["","paragraph.227"],"code":"**Config options:**"},{"id":"/root/children/227/children/0","type":"strong","loc":{"start":65809,"end":65828,"line":{"s":1088,"e":1088,"code":["**Config options:**"]},"column":{"s":0,"e":19}},"dim":["","paragraph.227","strong.0"],"code":"**Config options:**"},{"id":"/root/children/227/children/0/children/0","type":"text","loc":{"start":65811,"end":65826,"line":{"s":1088,"e":1088,"code":["**Config options:**"]},"column":{"s":2,"e":17}},"dim":["","paragraph.227","strong.0","text.0"],"code":"Config options:"},{"id":"/root/children/228","type":"paragraph","loc":{"start":65830,"end":67179,"line":{"s":1090,"e":1099,"code":["| Option                | Type    | Default | Description                                                                            |","| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |","| `width`               | number  | 600     | Spec width                                                                             |","| `height`              | number  | 500     | Spec height                                                                            |","| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |","| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |","| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |","| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |","| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |","| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |"]},"column":{"s":0,"e":134}},"dim":["","paragraph.228"],"code":"| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| `width`               | number  | 600     | Spec width                                                                             |\n| `height`              | number  | 500     | Spec height                                                                            |\n| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |\n| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |"},{"id":"/root/children/228/children/0","type":"text","loc":{"start":65830,"end":66102,"line":{"s":1090,"e":1092,"code":["| Option                | Type    | Default | Description                                                                            |","| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |","| `width`               | number  | 600     | Spec width                                                                             |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.228","text.0"],"code":"| Option                | Type    | Default | Description                                                                            |\n| --------------------- | ------- | ------- | -------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/228/children/1","type":"inlineCode","loc":{"start":66102,"end":66109,"line":{"s":1092,"e":1092,"code":["| `width`               | number  | 600     | Spec width                                                                             |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.228","inlineCode.1"],"code":"`width`"},{"id":"/root/children/228/children/2","type":"text","loc":{"start":66109,"end":66237,"line":{"s":1092,"e":1093,"code":["| `width`               | number  | 600     | Spec width                                                                             |","| `height`              | number  | 500     | Spec height                                                                            |"]},"column":{"s":9,"e":2}},"dim":["","paragraph.228","text.2"],"code":"               | number  | 600     | Spec width                                                                             |\n| "},{"id":"/root/children/228/children/3","type":"inlineCode","loc":{"start":66237,"end":66245,"line":{"s":1093,"e":1093,"code":["| `height`              | number  | 500     | Spec height                                                                            |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.228","inlineCode.3"],"code":"`height`"},{"id":"/root/children/228/children/4","type":"text","loc":{"start":66245,"end":66372,"line":{"s":1093,"e":1094,"code":["| `height`              | number  | 500     | Spec height                                                                            |","| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |"]},"column":{"s":10,"e":2}},"dim":["","paragraph.228","text.4"],"code":"              | number  | 500     | Spec height                                                                            |\n| "},{"id":"/root/children/228/children/5","type":"inlineCode","loc":{"start":66372,"end":66392,"line":{"s":1094,"e":1094,"code":["| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |"]},"column":{"s":2,"e":22}},"dim":["","paragraph.228","inlineCode.5"],"code":"`showQuadrantLabels`"},{"id":"/root/children/228/children/6","type":"text","loc":{"start":66392,"end":66507,"line":{"s":1094,"e":1095,"code":["| `showQuadrantLabels`  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |","| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |"]},"column":{"s":22,"e":2}},"dim":["","paragraph.228","text.6"],"code":"  | boolean | —       | Show \"@Urgent & Important\" etc labels                                                  |\n| "},{"id":"/root/children/228/children/7","type":"inlineCode","loc":{"start":66507,"end":66517,"line":{"s":1095,"e":1095,"code":["| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.228","inlineCode.7"],"code":"`useCurve`"},{"id":"/root/children/228/children/8","type":"text","loc":{"start":66517,"end":66642,"line":{"s":1095,"e":1096,"code":["| `useCurve`            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |","| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |"]},"column":{"s":12,"e":2}},"dim":["","paragraph.228","text.8"],"code":"            | boolean | —       | User-defined links: true = quadratic bezier, false = straight tapered line             |\n| "},{"id":"/root/children/228/children/9","type":"inlineCode","loc":{"start":66642,"end":66663,"line":{"s":1096,"e":1096,"code":["| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |"]},"column":{"s":2,"e":23}},"dim":["","paragraph.228","inlineCode.9"],"code":"`useCurveInConflicts`"},{"id":"/root/children/228/children/10","type":"text","loc":{"start":66663,"end":66777,"line":{"s":1096,"e":1097,"code":["| `useCurveInConflicts` | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |","| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |"]},"column":{"s":23,"e":2}},"dim":["","paragraph.228","text.10"],"code":" | boolean | —       | Conflict edges: true = bezier, false = straight line                                   |\n| "},{"id":"/root/children/228/children/11","type":"inlineCode","loc":{"start":66777,"end":66786,"line":{"s":1097,"e":1097,"code":["| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |"]},"column":{"s":2,"e":11}},"dim":["","paragraph.228","inlineCode.11"],"code":"`connect`"},{"id":"/root/children/228/children/12","type":"text","loc":{"start":66786,"end":66912,"line":{"s":1097,"e":1098,"code":["| `connect`             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |","| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |"]},"column":{"s":11,"e":2}},"dim":["","paragraph.228","text.12"],"code":"             | boolean | —       | Auto-detect same-heading tasks across quadrants and connect with dashed conflict edges |\n| "},{"id":"/root/children/228/children/13","type":"inlineCode","loc":{"start":66912,"end":66919,"line":{"s":1098,"e":1098,"code":["| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.228","inlineCode.13"],"code":"`links`"},{"id":"/root/children/228/children/14","type":"text","loc":{"start":66919,"end":66975,"line":{"s":1098,"e":1098,"code":["| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |"]},"column":{"s":9,"e":65}},"dim":["","paragraph.228","text.14"],"code":"               | array   | —       | User-defined links "},{"id":"/root/children/228/children/15","type":"inlineCode","loc":{"start":66975,"end":67000,"line":{"s":1098,"e":1098,"code":["| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |"]},"column":{"s":65,"e":90}},"dim":["","paragraph.228","inlineCode.15"],"code":"`[[source, target], ...]`"},{"id":"/root/children/228/children/16","type":"text","loc":{"start":67000,"end":67016,"line":{"s":1098,"e":1098,"code":["| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |"]},"column":{"s":90,"e":106}},"dim":["","paragraph.228","text.16"],"code":" where each has "},{"id":"/root/children/228/children/17","type":"inlineCode","loc":{"start":67016,"end":67039,"line":{"s":1098,"e":1098,"code":["| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |"]},"column":{"s":106,"e":129}},"dim":["","paragraph.228","inlineCode.17"],"code":"`{fragmentUrl, sector}`"},{"id":"/root/children/228/children/18","type":"text","loc":{"start":67039,"end":67047,"line":{"s":1098,"e":1099,"code":["| `links`               | array   | —       | User-defined links `[[source, target], ...]` where each has `{fragmentUrl, sector}`    |","| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |"]},"column":{"s":129,"e":2}},"dim":["","paragraph.228","text.18"],"code":"    |\n| "},{"id":"/root/children/228/children/19","type":"inlineCode","loc":{"start":67047,"end":67058,"line":{"s":1099,"e":1099,"code":["| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |"]},"column":{"s":2,"e":13}},"dim":["","paragraph.228","inlineCode.19"],"code":"`allowVote`"},{"id":"/root/children/228/children/20","type":"text","loc":{"start":67058,"end":67179,"line":{"s":1099,"e":1099,"code":["| `allowVote`           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |"]},"column":{"s":13,"e":134}},"dim":["","paragraph.228","text.20"],"code":"           | boolean | —       | When true, clicking a task opens the voting touchbar instead of navigating             |"},{"id":"/root/children/229","type":"paragraph","loc":{"start":67181,"end":67200,"line":{"s":1101,"e":1101,"code":["**Path utilities:**"]},"column":{"s":0,"e":19}},"dim":["","paragraph.229"],"code":"**Path utilities:**"},{"id":"/root/children/229/children/0","type":"strong","loc":{"start":67181,"end":67200,"line":{"s":1101,"e":1101,"code":["**Path utilities:**"]},"column":{"s":0,"e":19}},"dim":["","paragraph.229","strong.0"],"code":"**Path utilities:**"},{"id":"/root/children/229/children/0/children/0","type":"text","loc":{"start":67183,"end":67198,"line":{"s":1101,"e":1101,"code":["**Path utilities:**"]},"column":{"s":2,"e":17}},"dim":["","paragraph.229","strong.0","text.0"],"code":"Path utilities:"},{"id":"/root/children/230","type":"list","loc":{"start":67202,"end":67505,"line":{"s":1103,"e":1104,"code":["- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering).","- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point."]},"column":{"s":0,"e":121}},"dim":["","list.230"],"code":"- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering).\n- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point.","symbName":"list","symbRange":[67507,67904],"symbRangeL":[1103,1111],"outerCode":"- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point.\n\n**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes.\n\n## Traps pattern (cleanup on route leave)\n\n`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:","outerHtml":"<ul><li>`taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point.</li></ul>\n\n<p><b>Conflict edge rendering:</b> Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes.</p>\n\n<h2>Traps pattern (cleanup on route leave)</h2>\n\n<p>`commands.js` provides a <b>traps</b> mechanism to revert side effects when a `@command` route is left:</p>"},{"id":"/root/children/230/children/0","type":"listItem","loc":{"start":67202,"end":67383,"line":{"s":1103,"e":1103,"code":["- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."]},"column":{"s":0,"e":181}},"dim":["","list.230","listItem.0"],"code":"- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."},{"id":"/root/children/230/children/0/children/0","type":"paragraph","loc":{"start":67204,"end":67383,"line":{"s":1103,"e":1103,"code":["- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."]},"column":{"s":2,"e":181}},"dim":["","list.230","listItem.0","paragraph.0"],"code":"`taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."},{"id":"/root/children/230/children/0/children/0/children/0","type":"inlineCode","loc":{"start":67204,"end":67258,"line":{"s":1103,"e":1103,"code":["- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."]},"column":{"s":2,"e":56}},"dim":["","list.230","listItem.0","paragraph.0","inlineCode.0"],"code":"`taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)`"},{"id":"/root/children/230/children/0/children/0/children/1","type":"text","loc":{"start":67258,"end":67305,"line":{"s":1103,"e":1103,"code":["- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."]},"column":{"s":56,"e":103}},"dim":["","list.230","listItem.0","paragraph.0","text.1"],"code":" — samples a straight line with tapered width. "},{"id":"/root/children/230/children/0/children/0/children/2","type":"inlineCode","loc":{"start":67305,"end":67320,"line":{"s":1103,"e":1103,"code":["- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."]},"column":{"s":103,"e":118}},"dim":["","list.230","listItem.0","paragraph.0","inlineCode.2"],"code":"`lineOnly=true`"},{"id":"/root/children/230/children/0/children/0/children/3","type":"text","loc":{"start":67320,"end":67383,"line":{"s":1103,"e":1103,"code":["- `taperedLine(x1, y1, x2, y2, w1, w2, steps, lineOnly)` — samples a straight line with tapered width. `lineOnly=true` returns an open centerline path (for dashed stroke rendering)."]},"column":{"s":118,"e":181}},"dim":["","list.230","listItem.0","paragraph.0","text.3"],"code":" returns an open centerline path (for dashed stroke rendering)."},{"id":"/root/children/230/children/1","type":"listItem","loc":{"start":67384,"end":67505,"line":{"s":1104,"e":1104,"code":["- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point."]},"column":{"s":0,"e":121}},"dim":["","list.230","listItem.1"],"code":"- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point."},{"id":"/root/children/230/children/1/children/0","type":"paragraph","loc":{"start":67386,"end":67505,"line":{"s":1104,"e":1104,"code":["- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point."]},"column":{"s":2,"e":121}},"dim":["","list.230","listItem.1","paragraph.0"],"code":"`taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point."},{"id":"/root/children/230/children/1/children/0/children/0","type":"inlineCode","loc":{"start":67386,"end":67450,"line":{"s":1104,"e":1104,"code":["- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point."]},"column":{"s":2,"e":66}},"dim":["","list.230","listItem.1","paragraph.0","inlineCode.0"],"code":"`taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)`"},{"id":"/root/children/230/children/1/children/0/children/1","type":"text","loc":{"start":67450,"end":67505,"line":{"s":1104,"e":1104,"code":["- `taperedBezier(x1, y1, cx, cy, x2, y2, w1, w2, steps, lineOnly)` — same for quadratic bezier curves with control point."]},"column":{"s":66,"e":121}},"dim":["","list.230","listItem.1","paragraph.0","text.1"],"code":" — same for quadratic bezier curves with control point."},{"id":"/root/children/231","type":"paragraph","loc":{"start":67507,"end":67759,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":0,"e":252}},"dim":["","paragraph.231"],"code":"**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."},{"id":"/root/children/231/children/0","type":"strong","loc":{"start":67507,"end":67535,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":0,"e":28}},"dim":["","paragraph.231","strong.0"],"code":"**Conflict edge rendering:**"},{"id":"/root/children/231/children/0/children/0","type":"text","loc":{"start":67509,"end":67533,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":2,"e":26}},"dim":["","paragraph.231","strong.0","text.0"],"code":"Conflict edge rendering:"},{"id":"/root/children/231/children/1","type":"text","loc":{"start":67535,"end":67546,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":28,"e":39}},"dim":["","paragraph.231","text.1"],"code":" Uses Vega "},{"id":"/root/children/231/children/2","type":"inlineCode","loc":{"start":67546,"end":67552,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":39,"e":45}},"dim":["","paragraph.231","inlineCode.2"],"code":"`path`"},{"id":"/root/children/231/children/3","type":"text","loc":{"start":67552,"end":67563,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":45,"e":56}},"dim":["","paragraph.231","text.3"],"code":" mark with "},{"id":"/root/children/231/children/4","type":"inlineCode","loc":{"start":67563,"end":67582,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":56,"e":75}},"dim":["","paragraph.231","inlineCode.4"],"code":"`stroke: \"#ff0066\"`"},{"id":"/root/children/231/children/5","type":"text","loc":{"start":67582,"end":67584,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":75,"e":77}},"dim":["","paragraph.231","text.5"],"code":", "},{"id":"/root/children/231/children/6","type":"inlineCode","loc":{"start":67584,"end":67604,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":77,"e":97}},"dim":["","paragraph.231","inlineCode.6"],"code":"`strokeDash: [3, 3]`"},{"id":"/root/children/231/children/7","type":"text","loc":{"start":67604,"end":67606,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":97,"e":99}},"dim":["","paragraph.231","text.7"],"code":", "},{"id":"/root/children/231/children/8","type":"inlineCode","loc":{"start":67606,"end":67625,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":99,"e":118}},"dim":["","paragraph.231","inlineCode.8"],"code":"`fill: transparent`"},{"id":"/root/children/231/children/9","type":"text","loc":{"start":67625,"end":67652,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":118,"e":145}},"dim":["","paragraph.231","text.9"],"code":". The path is computed via "},{"id":"/root/children/231/children/10","type":"inlineCode","loc":{"start":67652,"end":67665,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":145,"e":158}},"dim":["","paragraph.231","inlineCode.10"],"code":"`taperedLine`"},{"id":"/root/children/231/children/11","type":"text","loc":{"start":67665,"end":67669,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":158,"e":162}},"dim":["","paragraph.231","text.11"],"code":" or "},{"id":"/root/children/231/children/12","type":"inlineCode","loc":{"start":67669,"end":67684,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":162,"e":177}},"dim":["","paragraph.231","inlineCode.12"],"code":"`taperedBezier`"},{"id":"/root/children/231/children/13","type":"text","loc":{"start":67684,"end":67688,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":177,"e":181}},"dim":["","paragraph.231","text.13"],"code":" in "},{"id":"/root/children/231/children/14","type":"inlineCode","loc":{"start":67688,"end":67698,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":181,"e":191}},"dim":["","paragraph.231","inlineCode.14"],"code":"`lineOnly`"},{"id":"/root/children/231/children/15","type":"text","loc":{"start":67698,"end":67759,"line":{"s":1106,"e":1106,"code":["**Conflict edge rendering:** Uses Vega `path` mark with `stroke: \"#ff0066\"`, `strokeDash: [3, 3]`, `fill: transparent`. The path is computed via `taperedLine` or `taperedBezier` in `lineOnly` mode — producing a centerline that Vega strokes with dashes."]},"column":{"s":191,"e":252}},"dim":["","paragraph.231","text.15"],"code":" mode — producing a centerline that Vega strokes with dashes."},{"id":"/root/children/232","type":"heading","loc":{"start":67761,"end":67802,"line":{"s":1108,"e":1108,"code":["## Traps pattern (cleanup on route leave)"]},"column":{"s":0,"e":41}},"dim":["","heading.232"],"code":"## Traps pattern (cleanup on route leave)","symbName":"heading","symbRange":[67804,70510],"symbRangeL":[1108,1133],"outerCode":"\n`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:\n\n- `getCommandHandler()` creates a `traps = []` array per invocation\n- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`\n- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`\n- On route change, `cleanup()` iterates through all traps and calls each one `await`\n- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`\n\n**Current trap users:**\n\n| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |\n| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |\n| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |\n| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |\n| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |\n\n**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages.\n\n**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore.","outerHtml":"\n<p>`commands.js` provides a <b>traps</b> mechanism to revert side effects when a `@command` route is left:</p>\n\n<ul><li>`getCommandHandler()` creates a `traps = []` array per invocation</li><li>Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`</li><li>Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`</li><li>On route change, `cleanup()` iterates through all traps and calls each one `await`</li><li>The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`</li></ul>\n\n<p><b>Current trap users:</b></p>\n\n<p>| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |\n| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/<i> TODO traps signalId </i>/}` — placeholder                                                              |\n| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |\n| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |\n| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |</p>\n\n<p><b>When to use:</b> Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages.</p>\n\n<p><b>Caveat — don't capture inline style to restore:</b> The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. <b>Always restore to `\"\"`</b> (remove inline style → CSS cascade takes over), never capture `style.*` for later restore.</p>"},{"id":"/root/children/232/children/0","type":"text","loc":{"start":67764,"end":67802,"line":{"s":1108,"e":1108,"code":["## Traps pattern (cleanup on route leave)"]},"column":{"s":3,"e":41}},"dim":["","heading.232","text.0"],"code":"Traps pattern (cleanup on route leave)"},{"id":"/root/children/233","type":"paragraph","loc":{"start":67804,"end":67904,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":0,"e":100}},"dim":["","paragraph.233"],"code":"`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"},{"id":"/root/children/233/children/0","type":"inlineCode","loc":{"start":67804,"end":67817,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":0,"e":13}},"dim":["","paragraph.233","inlineCode.0"],"code":"`commands.js`"},{"id":"/root/children/233/children/1","type":"text","loc":{"start":67817,"end":67829,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":13,"e":25}},"dim":["","paragraph.233","text.1"],"code":" provides a "},{"id":"/root/children/233/children/2","type":"strong","loc":{"start":67829,"end":67838,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":25,"e":34}},"dim":["","paragraph.233","strong.2"],"code":"**traps**"},{"id":"/root/children/233/children/2/children/0","type":"text","loc":{"start":67831,"end":67836,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":27,"e":32}},"dim":["","paragraph.233","strong.2","text.0"],"code":"traps"},{"id":"/root/children/233/children/3","type":"text","loc":{"start":67838,"end":67879,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":34,"e":75}},"dim":["","paragraph.233","text.3"],"code":" mechanism to revert side effects when a "},{"id":"/root/children/233/children/4","type":"inlineCode","loc":{"start":67879,"end":67889,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":75,"e":85}},"dim":["","paragraph.233","inlineCode.4"],"code":"`@command`"},{"id":"/root/children/233/children/5","type":"text","loc":{"start":67889,"end":67904,"line":{"s":1110,"e":1110,"code":["`commands.js` provides a **traps** mechanism to revert side effects when a `@command` route is left:"]},"column":{"s":85,"e":100}},"dim":["","paragraph.233","text.5"],"code":" route is left:"},{"id":"/root/children/234","type":"list","loc":{"start":67906,"end":68305,"line":{"s":1112,"e":1116,"code":["- `getCommandHandler()` creates a `traps = []` array per invocation","- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`","- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`","- On route change, `cleanup()` iterates through all traps and calls each one `await`","- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`"]},"column":{"s":0,"e":69}},"dim":["","list.234"],"code":"- `getCommandHandler()` creates a `traps = []` array per invocation\n- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`\n- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`\n- On route change, `cleanup()` iterates through all traps and calls each one `await`\n- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`","symbName":"list","symbRange":[68307,70554],"symbRangeL":[1112,1135],"outerCode":"- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`\n- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`\n- On route change, `cleanup()` iterates through all traps and calls each one `await`\n- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`\n\n**Current trap users:**\n\n| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |\n| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |\n| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |\n| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |\n| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |\n\n**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages.\n\n**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore.\n\n## cmdVotingCampaign.js — link persistence","outerHtml":"<ul><li>Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`</li><li>Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`</li><li>On route change, `cleanup()` iterates through all traps and calls each one `await`</li><li>The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`</li></ul>\n\n<p><b>Current trap users:</b></p>\n\n<p>| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |\n| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/<i> TODO traps signalId </i>/}` — placeholder                                                              |\n| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |\n| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |\n| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |</p>\n\n<p><b>When to use:</b> Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages.</p>\n\n<p><b>Caveat — don't capture inline style to restore:</b> The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. <b>Always restore to `\"\"`</b> (remove inline style → CSS cascade takes over), never capture `style.*` for later restore.</p>\n\n<h2>cmdVotingCampaign.js — link persistence</h2>"},{"id":"/root/children/234/children/0","type":"listItem","loc":{"start":67906,"end":67973,"line":{"s":1112,"e":1112,"code":["- `getCommandHandler()` creates a `traps = []` array per invocation"]},"column":{"s":0,"e":67}},"dim":["","list.234","listItem.0"],"code":"- `getCommandHandler()` creates a `traps = []` array per invocation"},{"id":"/root/children/234/children/0/children/0","type":"paragraph","loc":{"start":67908,"end":67973,"line":{"s":1112,"e":1112,"code":["- `getCommandHandler()` creates a `traps = []` array per invocation"]},"column":{"s":2,"e":67}},"dim":["","list.234","listItem.0","paragraph.0"],"code":"`getCommandHandler()` creates a `traps = []` array per invocation"},{"id":"/root/children/234/children/0/children/0/children/0","type":"inlineCode","loc":{"start":67908,"end":67929,"line":{"s":1112,"e":1112,"code":["- `getCommandHandler()` creates a `traps = []` array per invocation"]},"column":{"s":2,"e":23}},"dim":["","list.234","listItem.0","paragraph.0","inlineCode.0"],"code":"`getCommandHandler()`"},{"id":"/root/children/234/children/0/children/0/children/1","type":"text","loc":{"start":67929,"end":67940,"line":{"s":1112,"e":1112,"code":["- `getCommandHandler()` creates a `traps = []` array per invocation"]},"column":{"s":23,"e":34}},"dim":["","list.234","listItem.0","paragraph.0","text.1"],"code":" creates a "},{"id":"/root/children/234/children/0/children/0/children/2","type":"inlineCode","loc":{"start":67940,"end":67952,"line":{"s":1112,"e":1112,"code":["- `getCommandHandler()` creates a `traps = []` array per invocation"]},"column":{"s":34,"e":46}},"dim":["","list.234","listItem.0","paragraph.0","inlineCode.2"],"code":"`traps = []`"},{"id":"/root/children/234/children/0/children/0/children/3","type":"text","loc":{"start":67952,"end":67973,"line":{"s":1112,"e":1112,"code":["- `getCommandHandler()` creates a `traps = []` array per invocation"]},"column":{"s":46,"e":67}},"dim":["","list.234","listItem.0","paragraph.0","text.3"],"code":" array per invocation"},{"id":"/root/children/234/children/1","type":"listItem","loc":{"start":67974,"end":68072,"line":{"s":1113,"e":1113,"code":["- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`"]},"column":{"s":0,"e":98}},"dim":["","list.234","listItem.1"],"code":"- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`"},{"id":"/root/children/234/children/1/children/0","type":"paragraph","loc":{"start":67976,"end":68072,"line":{"s":1113,"e":1113,"code":["- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`"]},"column":{"s":2,"e":98}},"dim":["","list.234","listItem.1","paragraph.0"],"code":"Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`"},{"id":"/root/children/234/children/1/children/0/children/0","type":"text","loc":{"start":67976,"end":68027,"line":{"s":1113,"e":1113,"code":["- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`"]},"column":{"s":2,"e":53}},"dim":["","list.234","listItem.1","paragraph.0","text.0"],"code":"Each command handler receives it as the 4th param: "},{"id":"/root/children/234/children/1/children/0/children/1","type":"inlineCode","loc":{"start":68027,"end":68072,"line":{"s":1113,"e":1113,"code":["- Each command handler receives it as the 4th param: `function cmdFoo(command, api, PROXY, traps)`"]},"column":{"s":53,"e":98}},"dim":["","list.234","listItem.1","paragraph.0","inlineCode.1"],"code":"`function cmdFoo(command, api, PROXY, traps)`"},{"id":"/root/children/234/children/2","type":"listItem","loc":{"start":68073,"end":68150,"line":{"s":1114,"e":1114,"code":["- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`"]},"column":{"s":0,"e":77}},"dim":["","list.234","listItem.2"],"code":"- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`"},{"id":"/root/children/234/children/2/children/0","type":"paragraph","loc":{"start":68075,"end":68150,"line":{"s":1114,"e":1114,"code":["- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`"]},"column":{"s":2,"e":77}},"dim":["","list.234","listItem.2","paragraph.0"],"code":"Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`"},{"id":"/root/children/234/children/2/children/0/children/0","type":"text","loc":{"start":68075,"end":68108,"line":{"s":1114,"e":1114,"code":["- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`"]},"column":{"s":2,"e":35}},"dim":["","list.234","listItem.2","paragraph.0","text.0"],"code":"Handlers push cleanup functions: "},{"id":"/root/children/234/children/2/children/0/children/1","type":"inlineCode","loc":{"start":68108,"end":68150,"line":{"s":1114,"e":1114,"code":["- Handlers push cleanup functions: `traps.push(() => restoreOriginalState())`"]},"column":{"s":35,"e":77}},"dim":["","list.234","listItem.2","paragraph.0","inlineCode.1"],"code":"`traps.push(() => restoreOriginalState())`"},{"id":"/root/children/234/children/3","type":"listItem","loc":{"start":68151,"end":68235,"line":{"s":1115,"e":1115,"code":["- On route change, `cleanup()` iterates through all traps and calls each one `await`"]},"column":{"s":0,"e":84}},"dim":["","list.234","listItem.3"],"code":"- On route change, `cleanup()` iterates through all traps and calls each one `await`"},{"id":"/root/children/234/children/3/children/0","type":"paragraph","loc":{"start":68153,"end":68235,"line":{"s":1115,"e":1115,"code":["- On route change, `cleanup()` iterates through all traps and calls each one `await`"]},"column":{"s":2,"e":84}},"dim":["","list.234","listItem.3","paragraph.0"],"code":"On route change, `cleanup()` iterates through all traps and calls each one `await`"},{"id":"/root/children/234/children/3/children/0/children/0","type":"text","loc":{"start":68153,"end":68170,"line":{"s":1115,"e":1115,"code":["- On route change, `cleanup()` iterates through all traps and calls each one `await`"]},"column":{"s":2,"e":19}},"dim":["","list.234","listItem.3","paragraph.0","text.0"],"code":"On route change, "},{"id":"/root/children/234/children/3/children/0/children/1","type":"inlineCode","loc":{"start":68170,"end":68181,"line":{"s":1115,"e":1115,"code":["- On route change, `cleanup()` iterates through all traps and calls each one `await`"]},"column":{"s":19,"e":30}},"dim":["","list.234","listItem.3","paragraph.0","inlineCode.1"],"code":"`cleanup()`"},{"id":"/root/children/234/children/3/children/0/children/2","type":"text","loc":{"start":68181,"end":68228,"line":{"s":1115,"e":1115,"code":["- On route change, `cleanup()` iterates through all traps and calls each one `await`"]},"column":{"s":30,"e":77}},"dim":["","list.234","listItem.3","paragraph.0","text.2"],"code":" iterates through all traps and calls each one "},{"id":"/root/children/234/children/3/children/0/children/3","type":"inlineCode","loc":{"start":68228,"end":68235,"line":{"s":1115,"e":1115,"code":["- On route change, `cleanup()` iterates through all traps and calls each one `await`"]},"column":{"s":77,"e":84}},"dim":["","list.234","listItem.3","paragraph.0","inlineCode.3"],"code":"`await`"},{"id":"/root/children/234/children/4","type":"listItem","loc":{"start":68236,"end":68305,"line":{"s":1116,"e":1116,"code":["- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`"]},"column":{"s":0,"e":69}},"dim":["","list.234","listItem.4"],"code":"- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`"},{"id":"/root/children/234/children/4/children/0","type":"paragraph","loc":{"start":68238,"end":68305,"line":{"s":1116,"e":1116,"code":["- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`"]},"column":{"s":2,"e":69}},"dim":["","list.234","listItem.4","paragraph.0"],"code":"The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`"},{"id":"/root/children/234/children/4/children/0/children/0","type":"text","loc":{"start":68238,"end":68265,"line":{"s":1116,"e":1116,"code":["- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`"]},"column":{"s":2,"e":29}},"dim":["","list.234","listItem.4","paragraph.0","text.0"],"code":"The returned object shape: "},{"id":"/root/children/234/children/4/children/0/children/1","type":"inlineCode","loc":{"start":68265,"end":68305,"line":{"s":1116,"e":1116,"code":["- The returned object shape: `{ handler: asyncFn, cleanup: asyncFn }`"]},"column":{"s":29,"e":69}},"dim":["","list.234","listItem.4","paragraph.0","inlineCode.1"],"code":"`{ handler: asyncFn, cleanup: asyncFn }`"},{"id":"/root/children/235","type":"paragraph","loc":{"start":68307,"end":68330,"line":{"s":1118,"e":1118,"code":["**Current trap users:**"]},"column":{"s":0,"e":23}},"dim":["","paragraph.235"],"code":"**Current trap users:**"},{"id":"/root/children/235/children/0","type":"strong","loc":{"start":68307,"end":68330,"line":{"s":1118,"e":1118,"code":["**Current trap users:**"]},"column":{"s":0,"e":23}},"dim":["","paragraph.235","strong.0"],"code":"**Current trap users:**"},{"id":"/root/children/235/children/0/children/0","type":"text","loc":{"start":68309,"end":68328,"line":{"s":1118,"e":1118,"code":["**Current trap users:**"]},"column":{"s":2,"e":21}},"dim":["","paragraph.235","strong.0","text.0"],"code":"Current trap users:"},{"id":"/root/children/236","type":"paragraph","loc":{"start":68332,"end":69771,"line":{"s":1120,"e":1128,"code":["| File                                | Line | Trap                                                                                                           |","| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |","| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |","| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |","| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |","| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |","| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |","| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |","| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |"]},"column":{"s":0,"e":159}},"dim":["","paragraph.236"],"code":"| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |\n| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |\n| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |\n| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |\n| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |\n| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |"},{"id":"/root/children/236/children/0","type":"text","loc":{"start":68332,"end":68654,"line":{"s":1120,"e":1122,"code":["| File                                | Line | Trap                                                                                                           |","| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |","| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.236","text.0"],"code":"| File                                | Line | Trap                                                                                                           |\n| ----------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/236/children/1","type":"inlineCode","loc":{"start":68654,"end":68685,"line":{"s":1122,"e":1122,"code":["| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |"]},"column":{"s":2,"e":33}},"dim":["","paragraph.236","inlineCode.1"],"code":"`commands/cmdVotingCampaign.js`"},{"id":"/root/children/236/children/2","type":"text","loc":{"start":68685,"end":68699,"line":{"s":1122,"e":1122,"code":["| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |"]},"column":{"s":33,"e":47}},"dim":["","paragraph.236","text.2"],"code":"     | 388  | "},{"id":"/root/children/236/children/3","type":"inlineCode","loc":{"start":68699,"end":68729,"line":{"s":1122,"e":1122,"code":["| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |"]},"column":{"s":47,"e":77}},"dim":["","paragraph.236","inlineCode.3"],"code":"`() => (lastVega = undefined)`"},{"id":"/root/children/236/children/4","type":"text","loc":{"start":68729,"end":68814,"line":{"s":1122,"e":1123,"code":["| `commands/cmdVotingCampaign.js`     | 388  | `() => (lastVega = undefined)` — clears cached Vega spec                                                       |","| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":77,"e":2}},"dim":["","paragraph.236","text.4"],"code":" — clears cached Vega spec                                                       |\n| "},{"id":"/root/children/236/children/5","type":"inlineCode","loc":{"start":68814,"end":68836,"line":{"s":1123,"e":1123,"code":["| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":2,"e":24}},"dim":["","paragraph.236","inlineCode.5"],"code":"`commands/cmdScene.js`"},{"id":"/root/children/236/children/6","type":"text","loc":{"start":68836,"end":68859,"line":{"s":1123,"e":1123,"code":["| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":24,"e":47}},"dim":["","paragraph.236","text.6"],"code":"              | 24   | "},{"id":"/root/children/236/children/7","type":"inlineCode","loc":{"start":68859,"end":68890,"line":{"s":1123,"e":1123,"code":["| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":47,"e":78}},"dim":["","paragraph.236","inlineCode.7"],"code":"`() => clearInterval(signalId)`"},{"id":"/root/children/236/children/8","type":"text","loc":{"start":68890,"end":68974,"line":{"s":1123,"e":1124,"code":["| `commands/cmdScene.js`              | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |","| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":78,"e":2}},"dim":["","paragraph.236","text.8"],"code":" — stops interval timer                                                         |\n| "},{"id":"/root/children/236/children/9","type":"inlineCode","loc":{"start":68974,"end":68997,"line":{"s":1124,"e":1124,"code":["| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":2,"e":25}},"dim":["","paragraph.236","inlineCode.9"],"code":"`commands/cmdAlpine.js`"},{"id":"/root/children/236/children/10","type":"text","loc":{"start":68997,"end":69019,"line":{"s":1124,"e":1124,"code":["| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":25,"e":47}},"dim":["","paragraph.236","text.10"],"code":"             | 24   | "},{"id":"/root/children/236/children/11","type":"inlineCode","loc":{"start":69019,"end":69050,"line":{"s":1124,"e":1124,"code":["| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |"]},"column":{"s":47,"e":78}},"dim":["","paragraph.236","inlineCode.11"],"code":"`() => clearInterval(signalId)`"},{"id":"/root/children/236/children/12","type":"text","loc":{"start":69050,"end":69134,"line":{"s":1124,"e":1125,"code":["| `commands/cmdAlpine.js`             | 24   | `() => clearInterval(signalId)` — stops interval timer                                                         |","| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |"]},"column":{"s":78,"e":2}},"dim":["","paragraph.236","text.12"],"code":" — stops interval timer                                                         |\n| "},{"id":"/root/children/236/children/13","type":"inlineCode","loc":{"start":69134,"end":69155,"line":{"s":1125,"e":1125,"code":["| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |"]},"column":{"s":2,"e":23}},"dim":["","paragraph.236","inlineCode.13"],"code":"`commands/cmdDemo.js`"},{"id":"/root/children/236/children/14","type":"text","loc":{"start":69155,"end":69179,"line":{"s":1125,"e":1125,"code":["| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |"]},"column":{"s":23,"e":47}},"dim":["","paragraph.236","text.14"],"code":" (commands.js) | 81   | "},{"id":"/root/children/236/children/15","type":"inlineCode","loc":{"start":69179,"end":69214,"line":{"s":1125,"e":1125,"code":["| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |"]},"column":{"s":47,"e":82}},"dim":["","paragraph.236","inlineCode.15"],"code":"`() => {/* TODO traps signalId */}`"},{"id":"/root/children/236/children/16","type":"text","loc":{"start":69214,"end":69294,"line":{"s":1125,"e":1126,"code":["| `commands/cmdDemo.js` (commands.js) | 81   | `() => {/* TODO traps signalId */}` — placeholder                                                              |","| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":82,"e":2}},"dim":["","paragraph.236","text.16"],"code":" — placeholder                                                              |\n| "},{"id":"/root/children/236/children/17","type":"inlineCode","loc":{"start":69294,"end":69319,"line":{"s":1126,"e":1126,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":2,"e":27}},"dim":["","paragraph.236","inlineCode.17"],"code":"`player/player-tweaks.js`"},{"id":"/root/children/236/children/18","type":"text","loc":{"start":69319,"end":69339,"line":{"s":1126,"e":1126,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":27,"e":47}},"dim":["","paragraph.236","text.18"],"code":"           | 372  | "},{"id":"/root/children/236/children/19","type":"inlineCode","loc":{"start":69339,"end":69365,"line":{"s":1126,"e":1126,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":47,"e":73}},"dim":["","paragraph.236","inlineCode.19"],"code":"`observeVisibleElements()`"},{"id":"/root/children/236/children/20","type":"text","loc":{"start":69365,"end":69374,"line":{"s":1126,"e":1126,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":73,"e":82}},"dim":["","paragraph.236","text.20"],"code":" returns "},{"id":"/root/children/236/children/21","type":"inlineCode","loc":{"start":69374,"end":69393,"line":{"s":1126,"e":1126,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":82,"e":101}},"dim":["","paragraph.236","inlineCode.21"],"code":"`{traps, observer}`"},{"id":"/root/children/236/children/22","type":"text","loc":{"start":69393,"end":69411,"line":{"s":1126,"e":1126,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":101,"e":119}},"dim":["","paragraph.236","text.22"],"code":"; each trap calls "},{"id":"/root/children/236/children/23","type":"inlineCode","loc":{"start":69411,"end":69435,"line":{"s":1126,"e":1126,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |"]},"column":{"s":119,"e":143}},"dim":["","paragraph.236","inlineCode.23"],"code":"`observer.unobserve(el)`"},{"id":"/root/children/236/children/24","type":"text","loc":{"start":69435,"end":69454,"line":{"s":1126,"e":1127,"code":["| `player/player-tweaks.js`           | 372  | `observeVisibleElements()` returns `{traps, observer}`; each trap calls `observer.unobserve(el)`               |","| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |"]},"column":{"s":143,"e":2}},"dim":["","paragraph.236","text.24"],"code":"               |\n| "},{"id":"/root/children/236/children/25","type":"inlineCode","loc":{"start":69454,"end":69478,"line":{"s":1127,"e":1127,"code":["| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |"]},"column":{"s":2,"e":26}},"dim":["","paragraph.236","inlineCode.25"],"code":"`player/player-utils.js`"},{"id":"/root/children/236/children/26","type":"text","loc":{"start":69478,"end":69514,"line":{"s":1127,"e":1127,"code":["| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |"]},"column":{"s":26,"e":62}},"dim":["","paragraph.236","text.26"],"code":"            | 56   | Same pattern — "},{"id":"/root/children/236/children/27","type":"inlineCode","loc":{"start":69514,"end":69538,"line":{"s":1127,"e":1127,"code":["| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |"]},"column":{"s":62,"e":86}},"dim":["","paragraph.236","inlineCode.27"],"code":"`observeVisibleElements`"},{"id":"/root/children/236/children/28","type":"text","loc":{"start":69538,"end":69614,"line":{"s":1127,"e":1128,"code":["| `player/player-utils.js`            | 56   | Same pattern — `observeVisibleElements` pushes unobserve traps                                                 |","| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |"]},"column":{"s":86,"e":2}},"dim":["","paragraph.236","text.28"],"code":" pushes unobserve traps                                                 |\n| "},{"id":"/root/children/236/children/29","type":"inlineCode","loc":{"start":69614,"end":69630,"line":{"s":1128,"e":1128,"code":["| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |"]},"column":{"s":2,"e":18}},"dim":["","paragraph.236","inlineCode.29"],"code":"`ui-touchbar.js`"},{"id":"/root/children/236/children/30","type":"text","loc":{"start":69630,"end":69771,"line":{"s":1128,"e":1128,"code":["| `ui-touchbar.js`                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |"]},"column":{"s":18,"e":159}},"dim":["","paragraph.236","text.30"],"code":"                    | 598  | Pushes cleanup for touchpad init; also has its own separate traps array (line 4) with cleanup at lines 926-927 |"},{"id":"/root/children/237","type":"paragraph","loc":{"start":69773,"end":70018,"line":{"s":1130,"e":1130,"code":["**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages."]},"column":{"s":0,"e":245}},"dim":["","paragraph.237"],"code":"**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages."},{"id":"/root/children/237/children/0","type":"strong","loc":{"start":69773,"end":69789,"line":{"s":1130,"e":1130,"code":["**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages."]},"column":{"s":0,"e":16}},"dim":["","paragraph.237","strong.0"],"code":"**When to use:**"},{"id":"/root/children/237/children/0/children/0","type":"text","loc":{"start":69775,"end":69787,"line":{"s":1130,"e":1130,"code":["**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages."]},"column":{"s":2,"e":14}},"dim":["","paragraph.237","strong.0","text.0"],"code":"When to use:"},{"id":"/root/children/237/children/1","type":"text","loc":{"start":69789,"end":69943,"line":{"s":1130,"e":1130,"code":["**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages."]},"column":{"s":16,"e":170}},"dim":["","paragraph.237","text.1"],"code":" Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a "},{"id":"/root/children/237/children/2","type":"inlineCode","loc":{"start":69943,"end":69953,"line":{"s":1130,"e":1130,"code":["**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages."]},"column":{"s":170,"e":180}},"dim":["","paragraph.237","inlineCode.2"],"code":"`@command`"},{"id":"/root/children/237/children/3","type":"text","loc":{"start":69953,"end":70018,"line":{"s":1130,"e":1130,"code":["**When to use:** Any DOM mutation (overflow, classList, style, event listener) or timer (interval, timeout) that should be cleaned up when the user navigates away from a `@command` route. This prevents stale side effects persisting across pages."]},"column":{"s":180,"e":245}},"dim":["","paragraph.237","text.3"],"code":" route. This prevents stale side effects persisting across pages."},{"id":"/root/children/238","type":"paragraph","loc":{"start":70020,"end":70510,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":0,"e":490}},"dim":["","paragraph.238"],"code":"**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."},{"id":"/root/children/238/children/0","type":"strong","loc":{"start":70020,"end":70071,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":0,"e":51}},"dim":["","paragraph.238","strong.0"],"code":"**Caveat — don't capture inline style to restore:**"},{"id":"/root/children/238/children/0/children/0","type":"text","loc":{"start":70022,"end":70069,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":2,"e":49}},"dim":["","paragraph.238","strong.0","text.0"],"code":"Caveat — don't capture inline style to restore:"},{"id":"/root/children/238/children/1","type":"text","loc":{"start":70071,"end":70085,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":51,"e":65}},"dim":["","paragraph.238","text.1"],"code":" The handler ("},{"id":"/root/children/238/children/2","type":"inlineCode","loc":{"start":70085,"end":70102,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":65,"e":82}},"dim":["","paragraph.238","inlineCode.2"],"code":"`reloadDashboard`"},{"id":"/root/children/238/children/3","type":"text","loc":{"start":70102,"end":70189,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":82,"e":169}},"dim":["","paragraph.238","text.3"],"code":") can be called multiple times (initial load + MQ paper-reload events). If you capture "},{"id":"/root/children/238/children/4","type":"inlineCode","loc":{"start":70189,"end":70212,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":169,"e":192}},"dim":["","paragraph.238","inlineCode.4"],"code":"`ePaper.style.overflow`"},{"id":"/root/children/238/children/5","type":"text","loc":{"start":70212,"end":70289,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":192,"e":269}},"dim":["","paragraph.238","text.5"],"code":" on entry and restore it in the trap, a SECOND call will see the overflow as "},{"id":"/root/children/238/children/6","type":"inlineCode","loc":{"start":70289,"end":70299,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":269,"e":279}},"dim":["","paragraph.238","inlineCode.6"],"code":"`\"hidden\"`"},{"id":"/root/children/238/children/7","type":"text","loc":{"start":70299,"end":70357,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":279,"e":337}},"dim":["","paragraph.238","text.7"],"code":" (set by the first call) and push a trap that restores to "},{"id":"/root/children/238/children/8","type":"inlineCode","loc":{"start":70357,"end":70367,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":337,"e":347}},"dim":["","paragraph.238","inlineCode.8"],"code":"`\"hidden\"`"},{"id":"/root/children/238/children/9","type":"text","loc":{"start":70367,"end":70393,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":347,"e":373}},"dim":["","paragraph.238","text.9"],"code":" — defeating the cleanup. "},{"id":"/root/children/238/children/10","type":"strong","loc":{"start":70393,"end":70419,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":373,"e":399}},"dim":["","paragraph.238","strong.10"],"code":"**Always restore to `\"\"`**"},{"id":"/root/children/238/children/10/children/0","type":"text","loc":{"start":70395,"end":70413,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":375,"e":393}},"dim":["","paragraph.238","strong.10","text.0"],"code":"Always restore to "},{"id":"/root/children/238/children/10/children/1","type":"inlineCode","loc":{"start":70413,"end":70417,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":393,"e":397}},"dim":["","paragraph.238","strong.10","inlineCode.1"],"code":"`\"\"`"},{"id":"/root/children/238/children/11","type":"text","loc":{"start":70419,"end":70482,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":399,"e":462}},"dim":["","paragraph.238","text.11"],"code":" (remove inline style → CSS cascade takes over), never capture "},{"id":"/root/children/238/children/12","type":"inlineCode","loc":{"start":70482,"end":70491,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":462,"e":471}},"dim":["","paragraph.238","inlineCode.12"],"code":"`style.*`"},{"id":"/root/children/238/children/13","type":"text","loc":{"start":70491,"end":70510,"line":{"s":1132,"e":1132,"code":["**Caveat — don't capture inline style to restore:** The handler (`reloadDashboard`) can be called multiple times (initial load + MQ paper-reload events). If you capture `ePaper.style.overflow` on entry and restore it in the trap, a SECOND call will see the overflow as `\"hidden\"` (set by the first call) and push a trap that restores to `\"hidden\"` — defeating the cleanup. **Always restore to `\"\"`** (remove inline style → CSS cascade takes over), never capture `style.*` for later restore."]},"column":{"s":471,"e":490}},"dim":["","paragraph.238","text.13"],"code":" for later restore."},{"id":"/root/children/239","type":"heading","loc":{"start":70512,"end":70554,"line":{"s":1134,"e":1134,"code":["## cmdVotingCampaign.js — link persistence"]},"column":{"s":0,"e":42}},"dim":["","heading.239"],"code":"## cmdVotingCampaign.js — link persistence","symbName":"heading","symbRange":[70556,null],"symbRangeL":[1134,1135],"outerCode":"","outerHtml":""},{"id":"/root/children/239/children/0","type":"text","loc":{"start":70515,"end":70554,"line":{"s":1134,"e":1134,"code":["## cmdVotingCampaign.js — link persistence"]},"column":{"s":3,"e":42}},"dim":["","heading.239","text.0"],"code":"cmdVotingCampaign.js — link persistence"},{"id":"/root/children/240","type":"list","loc":{"start":70556,"end":70981,"line":{"s":1136,"e":1139,"code":["- `vegaLinks` array holds user-defined links between tasks","- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`","- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`","- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":0,"e":146}},"dim":["","list.240"],"code":"- `vegaLinks` array holds user-defined links between tasks\n- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`\n- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`\n- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`","symbName":"list","symbRange":[null,null],"symbRangeL":[1136,1139],"outerCode":"- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`\n- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`","outerHtml":"<ul><li>`loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`</li><li><b>Link added</b> in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`</li></ul>"},{"id":"/root/children/240/children/0","type":"listItem","loc":{"start":70556,"end":70614,"line":{"s":1136,"e":1136,"code":["- `vegaLinks` array holds user-defined links between tasks"]},"column":{"s":0,"e":58}},"dim":["","list.240","listItem.0"],"code":"- `vegaLinks` array holds user-defined links between tasks"},{"id":"/root/children/240/children/0/children/0","type":"paragraph","loc":{"start":70558,"end":70614,"line":{"s":1136,"e":1136,"code":["- `vegaLinks` array holds user-defined links between tasks"]},"column":{"s":2,"e":58}},"dim":["","list.240","listItem.0","paragraph.0"],"code":"`vegaLinks` array holds user-defined links between tasks"},{"id":"/root/children/240/children/0/children/0/children/0","type":"inlineCode","loc":{"start":70558,"end":70569,"line":{"s":1136,"e":1136,"code":["- `vegaLinks` array holds user-defined links between tasks"]},"column":{"s":2,"e":13}},"dim":["","list.240","listItem.0","paragraph.0","inlineCode.0"],"code":"`vegaLinks`"},{"id":"/root/children/240/children/0/children/0/children/1","type":"text","loc":{"start":70569,"end":70614,"line":{"s":1136,"e":1136,"code":["- `vegaLinks` array holds user-defined links between tasks"]},"column":{"s":13,"e":58}},"dim":["","list.240","listItem.0","paragraph.0","text.1"],"code":" array holds user-defined links between tasks"},{"id":"/root/children/240/children/1","type":"listItem","loc":{"start":70615,"end":70708,"line":{"s":1137,"e":1137,"code":["- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"]},"column":{"s":0,"e":93}},"dim":["","list.240","listItem.1"],"code":"- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"},{"id":"/root/children/240/children/1/children/0","type":"paragraph","loc":{"start":70617,"end":70708,"line":{"s":1137,"e":1137,"code":["- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"]},"column":{"s":2,"e":93}},"dim":["","list.240","listItem.1","paragraph.0"],"code":"`loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"},{"id":"/root/children/240/children/1/children/0/children/0","type":"inlineCode","loc":{"start":70617,"end":70634,"line":{"s":1137,"e":1137,"code":["- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"]},"column":{"s":2,"e":19}},"dim":["","list.240","listItem.1","paragraph.0","inlineCode.0"],"code":"`loadVegaLinks()`"},{"id":"/root/children/240/children/1/children/0/children/1","type":"text","loc":{"start":70634,"end":70637,"line":{"s":1137,"e":1137,"code":["- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"]},"column":{"s":19,"e":22}},"dim":["","list.240","listItem.1","paragraph.0","text.1"],"code":" / "},{"id":"/root/children/240/children/1/children/0/children/2","type":"inlineCode","loc":{"start":70637,"end":70654,"line":{"s":1137,"e":1137,"code":["- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"]},"column":{"s":22,"e":39}},"dim":["","list.240","listItem.1","paragraph.0","inlineCode.2"],"code":"`saveVegaLinks()`"},{"id":"/root/children/240/children/1/children/0/children/3","type":"text","loc":{"start":70654,"end":70674,"line":{"s":1137,"e":1137,"code":["- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"]},"column":{"s":39,"e":59}},"dim":["","list.240","listItem.1","paragraph.0","text.3"],"code":" sync to OuchDB doc "},{"id":"/root/children/240/children/1/children/0/children/4","type":"inlineCode","loc":{"start":70674,"end":70708,"line":{"s":1137,"e":1137,"code":["- `loadVegaLinks()` / `saveVegaLinks()` sync to OuchDB doc `{repoName}/campaign/{campaignId}`"]},"column":{"s":59,"e":93}},"dim":["","list.240","listItem.1","paragraph.0","inlineCode.4"],"code":"`{repoName}/campaign/{campaignId}`"},{"id":"/root/children/240/children/2","type":"listItem","loc":{"start":70709,"end":70834,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":0,"e":125}},"dim":["","list.240","listItem.2"],"code":"- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"},{"id":"/root/children/240/children/2/children/0","type":"paragraph","loc":{"start":70711,"end":70834,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":2,"e":125}},"dim":["","list.240","listItem.2","paragraph.0"],"code":"**Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"},{"id":"/root/children/240/children/2/children/0/children/0","type":"strong","loc":{"start":70711,"end":70725,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":2,"e":16}},"dim":["","list.240","listItem.2","paragraph.0","strong.0"],"code":"**Link added**"},{"id":"/root/children/240/children/2/children/0/children/0/children/0","type":"text","loc":{"start":70713,"end":70723,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":4,"e":14}},"dim":["","list.240","listItem.2","paragraph.0","strong.0","text.0"],"code":"Link added"},{"id":"/root/children/240/children/2/children/0/children/1","type":"text","loc":{"start":70725,"end":70729,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":16,"e":20}},"dim":["","list.240","listItem.2","paragraph.0","text.1"],"code":" in "},{"id":"/root/children/240/children/2/children/0/children/2","type":"inlineCode","loc":{"start":70729,"end":70741,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":20,"e":32}},"dim":["","list.240","listItem.2","paragraph.0","inlineCode.2"],"code":"`replantRef`"},{"id":"/root/children/240/children/2/children/0/children/3","type":"text","loc":{"start":70741,"end":70761,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":32,"e":52}},"dim":["","list.240","listItem.2","paragraph.0","text.3"],"code":" handler: pushes to "},{"id":"/root/children/240/children/2/children/0/children/4","type":"inlineCode","loc":{"start":70761,"end":70772,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":52,"e":63}},"dim":["","list.240","listItem.2","paragraph.0","inlineCode.4"],"code":"`vegaLinks`"},{"id":"/root/children/240/children/2/children/0/children/5","type":"text","loc":{"start":70772,"end":70780,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":63,"e":71}},"dim":["","list.240","listItem.2","paragraph.0","text.5"],"code":", calls "},{"id":"/root/children/240/children/2/children/0/children/6","type":"inlineCode","loc":{"start":70780,"end":70797,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":71,"e":88}},"dim":["","list.240","listItem.2","paragraph.0","inlineCode.6"],"code":"`saveVegaLinks()`"},{"id":"/root/children/240/children/2/children/0/children/7","type":"text","loc":{"start":70797,"end":70804,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":88,"e":95}},"dim":["","list.240","listItem.2","paragraph.0","text.7"],"code":", then "},{"id":"/root/children/240/children/2/children/0/children/8","type":"inlineCode","loc":{"start":70804,"end":70834,"line":{"s":1138,"e":1138,"code":["- **Link added** in `replantRef` handler: pushes to `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ link, ... }, true)`"]},"column":{"s":95,"e":125}},"dim":["","list.240","listItem.2","paragraph.0","inlineCode.8"],"code":"`echoOut({ link, ... }, true)`"},{"id":"/root/children/240/children/3","type":"listItem","loc":{"start":70835,"end":70981,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":0,"e":146}},"dim":["","list.240","listItem.3"],"code":"- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"},{"id":"/root/children/240/children/3/children/0","type":"paragraph","loc":{"start":70837,"end":70981,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":2,"e":146}},"dim":["","list.240","listItem.3","paragraph.0"],"code":"**Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"},{"id":"/root/children/240/children/3/children/0/children/0","type":"strong","loc":{"start":70837,"end":70853,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":2,"e":18}},"dim":["","list.240","listItem.3","paragraph.0","strong.0"],"code":"**Link removed**"},{"id":"/root/children/240/children/3/children/0/children/0/children/0","type":"text","loc":{"start":70839,"end":70851,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":4,"e":16}},"dim":["","list.240","listItem.3","paragraph.0","strong.0","text.0"],"code":"Link removed"},{"id":"/root/children/240/children/3/children/0/children/1","type":"text","loc":{"start":70853,"end":70857,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":18,"e":22}},"dim":["","list.240","listItem.3","paragraph.0","text.1"],"code":" in "},{"id":"/root/children/240/children/3/children/0/children/2","type":"inlineCode","loc":{"start":70857,"end":70870,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":22,"e":35}},"dim":["","list.240","listItem.3","paragraph.0","inlineCode.2"],"code":"`clickedLink`"},{"id":"/root/children/240/children/3/children/0/children/3","type":"text","loc":{"start":70870,"end":70901,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":35,"e":66}},"dim":["","list.240","listItem.3","paragraph.0","text.3"],"code":" signal listener: splices from "},{"id":"/root/children/240/children/3/children/0/children/4","type":"inlineCode","loc":{"start":70901,"end":70912,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":66,"e":77}},"dim":["","list.240","listItem.3","paragraph.0","inlineCode.4"],"code":"`vegaLinks`"},{"id":"/root/children/240/children/3/children/0/children/5","type":"text","loc":{"start":70912,"end":70920,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":77,"e":85}},"dim":["","list.240","listItem.3","paragraph.0","text.5"],"code":", calls "},{"id":"/root/children/240/children/3/children/0/children/6","type":"inlineCode","loc":{"start":70920,"end":70937,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":85,"e":102}},"dim":["","list.240","listItem.3","paragraph.0","inlineCode.6"],"code":"`saveVegaLinks()`"},{"id":"/root/children/240/children/3/children/0/children/7","type":"text","loc":{"start":70937,"end":70944,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":102,"e":109}},"dim":["","list.240","listItem.3","paragraph.0","text.7"],"code":", then "},{"id":"/root/children/240/children/3/children/0/children/8","type":"inlineCode","loc":{"start":70944,"end":70981,"line":{"s":1139,"e":1139,"code":["- **Link removed** in `clickedLink` signal listener: splices from `vegaLinks`, calls `saveVegaLinks()`, then `echoOut({ removedLink, ... }, true)`"]},"column":{"s":109,"e":146}},"dim":["","list.240","listItem.3","paragraph.0","inlineCode.8"],"code":"`echoOut({ removedLink, ... }, true)`"}]