graph { graph [engine:dot rankdir:LR applyCss:dark clusterize:false intention:semi grayscale:false desaturate:true] player_js___Analysis___Summary -> player_js___Analysis___Summary__Overview [reverse: false] player_js___Analysis___Summary -> player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd_ [reverse: false] player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd_ -> player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd____player_js___Analysis___Summary__Design_principles [reverse: false] player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd_ -> player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd____player_js___Analysis___Summary__Three_layers [reverse: false] player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd_ -> player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd____player_js___Analysis___Summary__Glass_menu_selector [reverse: false] player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd_ -> player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd____player_js___Analysis___Summary__Visualization [reverse: false] player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd_ -> player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd____player_js___Analysis___Summary__Data_dynamics [reverse: false] player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd_ -> player_js___Analysis___Summary__repolero_prototype___UI_concept__from_repolero_prototype_mdd____player_js___Analysis___Summary__UI_tricks [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__ssss___super_simple_static_site_generator [reverse: false] player_js___Analysis___Summary__ssss___super_simple_static_site_generator *= player_js___Analysis___Summary__ssss___super_simple_static_site_generator___player_js___Analysis___Summary__Principles [reverse: false] player_js___Analysis___Summary__ssss___super_simple_static_site_generator *= player_js___Analysis___Summary__ssss___super_simple_static_site_generator___player_js___Analysis___Summary__MPA_concept [reverse: false] player_js___Analysis___Summary__ssss___super_simple_static_site_generator *= player_js___Analysis___Summary__ssss___super_simple_static_site_generator___player_js___Analysis___Summary__Folder_structure [reverse: false] player_js___Analysis___Summary__ssss___super_simple_static_site_generator *= player_js___Analysis___Summary__ssss___super_simple_static_site_generator___player_js___Analysis___Summary__Tech_choices__from_ssss_mdd_ [reverse: false] player_js___Analysis___Summary__ssss___super_simple_static_site_generator *= player_js___Analysis___Summary__ssss___super_simple_static_site_generator___player_js___Analysis___Summary__Usage [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Build_Pipeline [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__jq_html_Templating [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__The_DOM_Skeleton__view_jq_html_ [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__Boot_Sequence [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__Inside_player__ [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__renderLayerMode___the_core_switch [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__How_the_HTML_sections_map_to_player_js_functions [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__Data_Flow__Search [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__Data_Flow__MQ__real_time_sync_ [reverse: false] player_js___Analysis___Summary__How_It_Works__HTML___JS_together_ *= player_js___Analysis___Summary__How_It_Works__HTML___JS_together____player_js___Analysis___Summary__Persistence_Model [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__ssss_js___The_Utility_Library [reverse: false] player_js___Analysis___Summary__ssss_js___The_Utility_Library *= player_js___Analysis___Summary__ssss_js___The_Utility_Library___player_js___Analysis___Summary__What_ssss_provides_to_player_js [reverse: false] player_js___Analysis___Summary__ssss_js___The_Utility_Library *= player_js___Analysis___Summary__ssss_js___The_Utility_Library___player_js___Analysis___Summary__ssss_Sub_modules [reverse: false] player_js___Analysis___Summary__ssss_js___The_Utility_Library *= player_js___Analysis___Summary__ssss_js___The_Utility_Library___player_js___Analysis___Summary__How_ssss_bootS4___drives_the_app [reverse: false] player_js___Analysis___Summary__ssss_js___The_Utility_Library *= player_js___Analysis___Summary__ssss_js___The_Utility_Library___player_js___Analysis___Summary__Key_design_notes [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Architecture [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Layers__routing_ [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__File_Summaries [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Layout_Engines__Eisenhower_Matrix_ [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Found_TASK__Comments [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Theme_structure [reverse: false] player_js___Analysis___Summary__Theme_structure *= player_js___Analysis___Summary__Theme_structure___player_js___Analysis___Summary__Dialog_backdrop_blur [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Observations [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Analysis__PROXY_vs_proxy [reverse: false] player_js___Analysis___Summary__Analysis__PROXY_vs_proxy *= player_js___Analysis___Summary__Analysis__PROXY_vs_proxy___player_js___Analysis___Summary__Intended_pattern [reverse: false] player_js___Analysis___Summary__Analysis__PROXY_vs_proxy *= player_js___Analysis___Summary__Analysis__PROXY_vs_proxy___player_js___Analysis___Summary__What_actually_happens [reverse: false] player_js___Analysis___Summary__Analysis__PROXY_vs_proxy *= player_js___Analysis___Summary__Analysis__PROXY_vs_proxy___player_js___Analysis___Summary__Violations___proxy_used_as_reader [reverse: false] player_js___Analysis___Summary__Analysis__PROXY_vs_proxy *= player_js___Analysis___Summary__Analysis__PROXY_vs_proxy___player_js___Analysis___Summary__Consequence [reverse: false] player_js___Analysis___Summary__Analysis__PROXY_vs_proxy *= player_js___Analysis___Summary__Analysis__PROXY_vs_proxy___player_js___Analysis___Summary__Fix_applied [reverse: false] player_js___Analysis___Summary *= player_js___Analysis___Summary__Refactoring__boot_____actions_js [reverse: false] player_js___Analysis___Summary__Refactoring__boot_____actions_js *= player_js___Analysis___Summary__Refactoring__boot_____actions_js___player_js___Analysis___Summary__What_changed [reverse: false] player_js___Analysis___Summary__Refactoring__boot_____actions_js *= player_js___Analysis___Summary__Refactoring__boot_____actions_js___player_js___Analysis___Summary__New_files [reverse: false] player_js___Analysis___Summary__Refactoring__boot_____actions_js *= player_js___Analysis___Summary__Refactoring__boot_____actions_js___player_js___Analysis___Summary__boot___in_player_js [reverse: false] player_js___Analysis___Summary__Refactoring__boot_____actions_js *= player_js___Analysis___Summary__Refactoring__boot_____actions_js___player_js___Analysis___Summary__Config_injection____script_id__app_config___ [reverse: false] player_js___Analysis___Summary__Refactoring__boot_____actions_js *= player_js___Analysis___Summary__Refactoring__boot_____actions_js___player_js___Analysis___Summary__Result_per_page [reverse: false] player_js___Analysis___Summary__Refactoring__boot_____actions_js *= player_js___Analysis___Summary__Refactoring__boot_____actions_js___player_js___Analysis___Summary__TODO__Production_optimizations__Brotli___Minify_ [reverse: false] Split_player_js_into_modules -> Split_player_js_into_modules__Done [reverse: false] Touchbar___Double_Touch -> Touchbar___Double_Touch__Done [reverse: false] Touchbar___Double_Touch -> Touchbar___Double_Touch__Known_gaps [reverse: false] Tabler -> Tabler__Done [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Done [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Use [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Touchbar_Actions [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Files_Changed [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Testing [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Key_conventions [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Voting_system [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Touchbar_system_API [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Architecture_quirks [reverse: false] Eisenhower_matrix -> Eisenhower_matrix__Heading_changes____Fragment_management [reverse: false] Eisenhower_matrix__Heading_changes____Fragment_management -> Eisenhower_matrix__Heading_changes____Fragment_management___Eisenhower_matrix__Files_changed [reverse: false] Eisenhower_matrix__Heading_changes____Fragment_management -> Eisenhower_matrix__Heading_changes____Fragment_management___Eisenhower_matrix__Dialog_rename [reverse: false] Eisenhower_matrix__Heading_changes____Fragment_management -> Eisenhower_matrix__Heading_changes____Fragment_management___Eisenhower_matrix__hasMetaData_filter__heading_report_ [reverse: false] Eisenhower_matrix__Heading_changes____Fragment_management -> Eisenhower_matrix__Heading_changes____Fragment_management___Eisenhower_matrix__fragmentUrl_system [reverse: false] Eisenhower_matrix__Heading_changes____Fragment_management -> Eisenhower_matrix__Heading_changes____Fragment_management___Eisenhower_matrix__replantRef_touchbar_action [reverse: false] Eisenhower_matrix__Heading_changes____Fragment_management -> Eisenhower_matrix__Heading_changes____Fragment_management___Eisenhower_matrix__URL_construction [reverse: false] Eisenhower_matrix__Heading_changes____Fragment_management -> Eisenhower_matrix__Heading_changes____Fragment_management___Eisenhower_matrix__SQL_rows [reverse: false] Eisenhower_matrix *= Eisenhower_matrix__OuchDB__Remote_CouchDB__PouchDB_ [reverse: false] Eisenhower_matrix__OuchDB__Remote_CouchDB__PouchDB_ *= Eisenhower_matrix__OuchDB__Remote_CouchDB__PouchDB____Eisenhower_matrix__CORS_conflict__Caddy___PouchDB_double_headers [reverse: false] Eisenhower_matrix *= Eisenhower_matrix__eisenhover_matrix_js___Vega_spec_builder [reverse: false] Eisenhower_matrix *= Eisenhower_matrix__Traps_pattern__cleanup_on_route_leave_ [reverse: false] Eisenhower_matrix *= Eisenhower_matrix__cmdVotingCampaign_js___link_persistence [reverse: false] player_js___Analysis___Summary [z: 9.5 @html: "
| player.js - Analysis & Summary |
| Overview 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.The HTML shell ( view.jq.html ) defines the DOM skeleton. player.js brings it alive. |
| repolero-prototype — UI concept from repolero-prototype.mdd |
| Design principles | |||
|
| Three layers | |||||||||||||||
|
| Glass menu selector Uses autocomplete libs: tomickigrzegorz/autocomplete, autoComplete.js, awesomplete |
| Visualization | ||
|
| Data dynamics | |||
|
| UI tricks | ||
|
| ssss — super simple static site generator repolero-prototype is built with ssss , a custom static site generator. |
| Principles | ||
|
| MPA concept Multi Page App — 80% static HTML, 10% backed SPA (portlets). Uses jq-html for templating, hash-routing for SPA layers. |
| Folder structure 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) |
| Tech choices from ssss.mdd | |||||||||||||||||||||||||||||||||||||||||||||
|
| Usage yarn serve , yarn watch , yoffee |
| Build Pipeline 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.): 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 All jq.html pages: view, reflect, rc, home, root, log, out (+ out.view, out.rc, out.root variants) 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 . The .jq.json structure (shared across pages): { "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 } 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> .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. What gets transformed: | ||||
| ||||
| What passes through untouched: | ||||
|
| jq-html Templating view.jq.html uses a custom templating engine called jq-html . It embeds jq expressions directly in HTML using ¦ (broken bar) delimiters. Three modes: | 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 |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. Common helpers used in the template: | ||||||||
| ||||||||
| How the template feeds player.js (in the boot <script> ): const env = JSON.parse('¦.site.env|tojson¦') const siteHotkey = JSON.parse('¦.site.hotkey|tojson¦') const mqBrokerURL = JSON.parse('¦"mq:/ws"|s4url|tojson¦') ... Server-side jq renders config into JSON strings. The JS boot script parses them and passes everything into player({...}) . |
| How It Works HTML + JS together |
| The DOM Skeleton view.jq.html The HTML defines five top-level containers, only one visible at a time: <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> Layers are toggled via .on / .off CSS classes by eSetOnOff() . |
| Boot Sequence 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 |
| Inside player 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 |
| renderLayerMode - the core switch This is the heart. It receives a targetLayer and toggles the DOM: 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 |
| How the HTML sections map to player.js functions | ||||||||||||||||||||||||||||||||
|
| Data Flow: Search 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) |
| Data Flow: MQ real-time sync 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', {...}) |
| Persistence Model All state is scoped by windowStoreKey(key) which prefixes with {idBui, repo} : | ||||||||||||||||||||||
|
| ssss.js - The Utility Library 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. |
| What ssss provides to player.js | ||||||||||||||||||||||||||||||||||||||||||
|
| ssss Sub-modules 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) |
| How ssss.bootS4 drives the app 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 The routeFallback is where player.js receives every hash change as parsed tokens, then calls renderLayerMode() . |
| Key design notes | ||||
|
| Architecture 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) |
| Layers routing | ||||||||||||||||||
|
| File Summaries | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
| Layout Engines Eisenhower Matrix | ||||||||||||||
| ||||||||||||||
| All active layout engines (boss2–5, normal) set two width fields per task: | ||||||||||||||
|
| Found TASK: Comments | ||||||
| ||||||
| Note: The autoZoom TASK in map-show.js has been resolved. |
| Theme structure Two LESS files serve the same HTML with different skins: | ||
| ||
| 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. |
| Dialog backdrop blur 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.). |
| Observations | ||||||||||||
| ||||||||||||
| Analysis: PROXY vs proxy |
| Intended pattern | ||
|
| What actually happens The builder pattern doesn't exist in the code. proxy is never written to — it's always a reader. Merge point (line 3288): Object.assign(PROXY, { mapShow, notyf, repoName, ... }) fills the singleton. 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. |
| Violations — proxy used as reader | ||||||||||||||||||||
|
| Consequence 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. |
| Fix applied Enforced the pattern: PROXY is the singleton, functions read from it directly. Changes in player.js: | ||||||
| ||||||
| Changes in external modules: | ||||||
| ||||||
| Refactoring: boot + actions.js |
| What changed 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) . |
| New files actions.js — shared window.* function registrations: 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) |
| boot in player.js 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 } opts: | |||||||||||||||||||||||||||
|
| Config injection: Each page has one jq expression that produces all config as JSON: <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> Note: .protocolRouter is passed through as-is (already a JSON string from the jq context). boot() handles parsing it. |