Commit graph

46 commits

Author SHA1 Message Date
doxa-maps-88655c70
413fa30d19 isMap per profile: a map-less leaf no longer bakes, or preloads, a map engine it never uses
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
generate-tree decided a single leaf's map-ness as "its own .vue
references an engine OR anything under the BUNDLE's src/ does", so a
map-less profile inherited it from a sibling. regions-dashboard/
regions-list shows a list and no map, but src/RegionCountryMap.vue made
it isMap=true. Its leaf loaded the Mapbox engine for nothing, and since
4a6886d2 its embed snippet also preloaded it.

A leaf's map-ness now follows the RELATIVE imports reachable from its
own .vue, bounded to its bundle, so a map several imports deep
(research-dashboard's DashboardMapPanel) still counts. An explicit
boolean isMap in <profile>.meta.json wins. Composites keep their
separate rule, since they do mount their siblings.

Measured: across all 34 profiles exactly one changed (regions-list,
true -> false). 18 map profiles keep isMap and their preload; 0 non-map
snippets carry one. On a served built tree regions-list makes 0 engine
requests and 0 api.mapbox.com requests and renders its list (673
elements, no errors), where before it downloaded the 1.75 MB engine.
regions-mfe still loads the engine and draws its map. The regenerated
files are only the regions-list leaf, the two manifest copies (still
byte-identical), the staging page and the gallery; check:sri passes.
2026-09-25 10:17:33 +00:00
doxa-maps-88655c70
a7d086619b Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 08:56:25 +00:00
doxa-maps-88655c70
4a6886d2c9 Embed snippets preload the Mapbox engine, so a partner page fetches it alongside the bundle
The card asked for Vue to be bundled inline to make maps faster. It
already is: vite.config.js externalises only jspdf, and every IIFE
carries its Vue runtime. The measurement (internal/measure-map-load.mjs,
cold, 10 Mbps / 60 ms, served built tree) found one serial runtime
fetch before first paint, on partner pages only. A page that pastes the
embed snippet has no leaf page to fetch mapbox-gl.js (~480 KB gz), so the
bundle requested it after it had downloaded, run and mounted. Leaf pages
already load it in parallel.

Map profiles' script snippets now start with a preload for the engine,
at the URL read from useMapInstance.js ENGINES.mapbox.js so the two can
never differ. It has no crossorigin attribute because
ensureEngineScript injects a plain <script>, and a mismatched preload is
not reused. Non-map profiles get no preload. A comment tells a partner
who switches to provider "maplibre" to delete the line.

Partner embed, before -> after (2 cold runs):
  doxa-simple-map  engine start 1.06 -> 0.20 s, done 2.26 -> 0.95 s,
                   style start 2.73 -> 1.94 s
  regions-mfe      engine start 2.67 -> 0.11 s, done 3.25 -> 1.13 s
                   (style start unchanged: the composite mounts its map late)
  gospel resources engine start 0.81 -> 0.27 s
One engine request per run: the preload is reused, never a double
download. The time until tiles are loaded is dominated by tiles, data
and drawing, and moved within noise.

Held, per dx-shepherd: no shared Vue, no inlined mapbox-gl, and lighter
basemaps are for the Driver to decide.
2026-09-25 08:56:09 +00:00
doxa-maps-88655c70
27151f39a6 Research Gallery: boot on a partner page under the CORS the live server actually sends
The doxa.life script embed was blocked on a server change: the live root
manifest.json answers without Access-Control-Allow-Origin, and the gallery
fetch()es it cross-origin, so no tab could mount. Measured on nuxtbase,
though, the same static layer DOES send ACAO on every path inside a folder
of the tree (doxa-maps-spa/<anything>, even a 404; assets/…).

So generate-tree writes a byte-identical manifest copy at
doxa-maps-spa/manifest.json in the same step as the root one (they cannot
diverge). The gallery reads it FIRST when the tree is on another origin (no
CORS error in the host's console) and the root first when same-origin.
Script URLs still resolve against the tree root, never the copy's folder.

check:gallery-embed:live-cors serves the tree with the live rule
(serve-strict LIVE_CORS=1: no ACAO on the root manifest only).
- The current gallery under it: "did not boot within 60s" (negative
  control, reproducing doxa.life).
- With this change: js 14, iframe 14, neighbour 14, linked 4. The default
  gate also passes all phases.
check:embed-targets, check:drop and check:public-hygiene pass with the copy
present.

The server-side CORS rule (serving/traefik-embed-headers.yaml) is still the
right long-term fix, and it needs server access. This unblocks the embed
without it once the tree is published.
2026-09-25 08:55:52 +00:00
doxa-maps-88655c70
2043384435 Merge remote-tracking branch 'origin/main' into exp-shepherd/pplr
# Conflicts:
#	internal/generate-tree.mjs
2026-09-25 07:40:35 +00:00
doxa-maps-88655c70
33bd7ca387 All People Groups loads fast on a light map; the Vision Card becomes a true funnel from data
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
ALL PEOPLE GROUPS. The Driver: "not even loading, takes a very long
time", and its DOXA pins were dark on a dark map.
- It downloaded the 7.5 MB people-groups-slim.json (22 columns) to draw
  ten fields. It now fetches people-groups-points.json: the same rows in
  column order, repeated strings in dictionaries, coordinates at 3
  decimals. 0.74 MB, generated deterministically by
  library/scripts/people-groups/build-points.mjs and staged into the tree
  by generate-tree. It falls back to the old file on an older tree.
- Pins no longer wait for the live prayer API. Every pin draws by GSEC
  from the file, and the DOXA overlay recolours when the API answers. The
  DOXA count reads "…" until then, not 0.
- Named style 'paper' (Mapbox light-v11 / MapLibre positron) instead of
  'ink'. The four categories keep their meaning; yellow and green are a
  step darker, and pins get a thin dark ring, so they read on near-white.
- Its words are in all 11 locales (allPeopleGroups.json), with a guarded
  useI18n and values passed to vue-i18n so {n} is not blanked.
Measured on a served built tree, 3 fresh runs each, median time to first
rendered pins: 8.3 s before, 5.2 s after, on localhost. This box renders
GL in software, so most of the remaining time is map start-up; the 10x
smaller download matters more on a real connection.

DOXA VISION CARD. The Driver: "the data doesn't make sense … super false
information". The card rotated four typed-in figures with no source.
It is now a proportional funnel, every number computed from data by
library/scripts/people-groups/build-vision-funnel.mjs:
  12,379 people groups   (IMB rows: one group in one country)
   7,341 unreached       (IMB LostnessPriority 0-2, < 2% evangelical)
   4,754 frontier        (LostnessPriority 0, < 0.1% evangelical)
   2,107 DOXA groups     (the DOXA prayer list, snapshot saved in data/;
                          1,745 of them frontier)
The first three nest; DOXA groups do not all sit inside frontier, so the
card says how many do. The rules are on the card ("How these are
counted") and in HOW-THE-NUMBERS-ARE-COUNTED.md, which also explains why
the total is not the 16-17 thousand of Joshua Project's count. Material
light, all 11 locales, right-to-left in Arabic, sources and dates shown.
The old fake stats are removed from the instances file, the frontier
page, the manifest, the staging page and the gallery.

CHECKS.
- check:vision-card-numbers recomputes the funnel independently (own CSV
  reader, the published rules) and compares with vision-funnel.json and,
  with --url, the rendered bars. Negative-tested: a corrupted count exits
  1. On the built tree: bars 12,379 / 7,341 / 4,754 / 2,107 and "1,745 of
  them are frontier", all matching.
- check:map-embed-cells measures PRODUCTION-MATRIX cells B and C for the
  seven gallery maps on a foreign origin; C proves the host's token is
  used by handing the host a distinct token and checking every Mapbox
  request. 14 of 14 pass.
2026-09-25 07:18:30 +00:00
doxa-maps-88655c70
e1d847028d PPLR data maps (WIP): move the ten PPLR research views into plugin-designs
Not for main yet. All ten views mount on the dev server with real data and no
page errors. Left: the coverage view's status still says 'sample of 6,000',
its choropleth needs checking, and the bundle is not built or tree-verified.
2026-09-25 06:33:49 +00:00
doxa-maps-88655c70
ce46d92998 Composite pages hand a ?tk= or pasted token to their nested maps, and ask when there is none
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
A composite's nested Mapbox maps read window.MAP_TOKEN, not the page's
profile-config. In a tree with no baked token (the public copy), the token-free
research-map-plus-grid page mounted before reading ?tk= or a stored token, so
its map stayed blank with no prompt. Now every leaf publishes the token it
resolves to window.MAP_TOKEN, and a token-free composite with no token renders
its content plus the paste-once prompt, then reloads with the token.

Verified in a browser against a tree regenerated with no token:
  research-map-plus-grid  no token: prompt shown | ?tk=: map drawn, Mapbox 6 OK / 0 errors
  dashboard-plus-grid     no token: prompt shown | ?tk=: map drawn, Mapbox 6 OK / 0 errors
Negative control (unpatched generator): research-map-plus-grid with ?tk= never
received the token (0 Mapbox requests) and showed no prompt without one.

Also: removed a personal health note the page template wrote into every leaf
page, and the Mapbox account names from three comments. Pages regenerated on
the latest main; nested-token, sri, hygiene, drop, parse, embed-targets pass.
2026-09-25 05:35:46 +00:00
doxa-maps-88655c70
193127e257 Stop publishing the template scaffold's placeholder pages
Some checks are pending
build-gate / build (push) Waiting to run
template-bundle is the scaffold DX copies to start a plugin: its source
ships, but it is not a plugin. The dev manifest already skips it by
name (discover.mjs), while the build published it, so the built studio
listed template-map-a and template-map-b as two blank-looking plugins
that the dev studio never showed.

generate-tree now skips it by the same name rule, so the built
manifest lists exactly what dev lists: 21 bundles, 33 profiles. The
committed tree is brought in line without a full regeneration: the
entry is removed from manifest.json and from the staging page's baked
copy, the two leaf pages are deleted, and the archived gallery is
regenerated from the manifest. It is not added to gatedProfiles, which
is the private-data list.

Verified: check:sri and check-manifest-covers-discovery pass, the
staging page renders with no errors, and a sweep of the built studio
shows 31 plugins and no template rows.
2026-09-25 04:05:59 +00:00
doxa-maps-88655c70
6ab560e42f Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:06:54 +00:00
doxa-maps-88655c70
3a2cf0e8f2 Research Gallery: embed on someone else's page, in both modes
The gallery is going onto the DX-life research page, and nothing had tried
it on a page it didn't generate. From a second origin, a bare
<script> + <doxa-maps-spa> drew over the host's header, and 8 of 13 tabs
showed an error card.

Three causes, each fixed in the shell:

- No :host box. The layout root is position:absolute, so it anchored to the
  viewport instead of the element. :host is now a positioned block with a
  default height the host can override.
- A foreign page sets none of the globals a leaf page bakes. Every data call
  hit the host's own origin and got HTML back. The manifest now publishes a
  `runtime` block (apiBaseUrl + the public token every leaf already carries).
  The shell fills window.MAP_APP_API_URL / window.MAP_TOKEN only when the page
  hasn't set them, and forwards tk in profile-config.
- Nested elements resolved assets to <tree>/../assets, a sibling pool the
  publisher doesn't ship (404 live). The shell now forwards an absolute
  <tree>/assets. This also fixes the iframe mode, where Cities with Internet
  and Gospel Resources were failing the same way.

Also adds a `tab` attribute (plus ?tab=) to deep-link into one map, and a
`tab-change` event for the host.

check:gallery-embed drives every tab in both modes from a second origin. It
passes here and fails on the previous tree (7 findings).

EMBED.md documents the contract. The Traefik template now sends CORS for the
whole tree: live manifest.json answers without Access-Control-Allow-Origin.
2026-09-24 19:03:37 +00:00
doxa-maps-88655c70
42c2279ee3 UPG Neighbor: build the five neighbor maps from source, not from a frozen IIFE in an iframe
Send Map, the Bloom cockpit, Flower Mesh, Lang Cluster and Flowers ran in
the Experimental shelf as a 976 KB prebuilt legacy bundle inside a sandboxed
iframe. Their original source survived in the old app-profiles/doxa-upg-neighbor
bundle, and it only imports library pieces that still exist, so it moves here
as-is: the unified hub becomes upg-neighbor.vue and the lenses go in src/.

The hub now picks its opening lens at setup rather than on mount. Switching
on mount tore Send Map down mid-initialisation and threw on every other lens.
The five instance ids stay the same, so existing embed URLs still work, and
the old legacyElement key still maps to a lens.

The legacy sandbox (data/legacy/upg-neighbor and its staging rows) is gone
because nothing loads it any more.
2026-09-24 18:47:14 +00:00
doxa-maps-88655c70
3a468c2bc3 The poster print preview rasterises too, and a deployment can ship sources without rasters
Some checks are pending
build-gate / build (push) Waiting to run
posters/*.webp is not shipped by default (42MB; bundling pushed the tree to 80MB,
git-packed to 57MiB, and the catalog rejected the push). The DOWNLOAD path has
handled that since d154a76 — PosterWall fetches posters/<id>.html and rasterises
it in the browser. The PRINT path did not: it handed an <img> a url it never
checked, so on a default drop the SAME poster downloaded fine and PRINTED as
"Poster not available yet." That is the bad first impression
IDEA-POSTER-EXPORT-404-ON-DROPPED-FOLDER-TRADEOFF called out by name.

Three things were missing; all three are here:

1. printPoster now climbs the same ladder as the download path — probe the webp,
   and only on a miss fetch the .html, rasterise, and verify PIXELS before opening
   the dialog (an SVG in an <img> is an isolated document: a lost stylesheet prints
   a black square with no error). Blob urls are held until unmount, not revoked at
   print time — the preview <img> is still reading from them.

2. publish-html.mjs wrote into public/assets/posters, which is a GENERATED MIRROR
   that every `bun run tree` rmSync's. Its output never survived, which is a large
   part of why the fallback still had no input months after it shipped. Retargeted
   at data/posters, the tracked source the mirror is made from.

3. bundlePosters is one boolean over a directory holding 40.9MB of rasters AND the
   28 sources, so it could only ever ship both or neither — and "neither" is what
   made both full-size paths 404. posterSources:true ships the sources without the
   rasters, the combination that was previously inexpressible. DEFAULT UNCHANGED:
   28 sources are 27.9MB raw (6.9MB packed, measured with pack-objects), real
   weight in a published tree that needs a deployment decision, not a silent one.

VERIFIED AT THE RENDERED LAYER, not by reading source. check:poster-print-fallback
clicks the real control with every posters/*.webp ABORTED at the network layer —
the default-drop condition forced rather than hoped for — and asserts the HUD
enters "generating the poster" and settles clean. Mutation-tested: removing the
fallback produces 4 failures including "no posters/*.html was fetched". The other
branch is verified too — with a webp present it is used directly, zero html
fetches, no rasterise — so the extra probe is paid only on a miss.

★ THE GATE WAS WRONG THREE TIMES FIRST, each time blaming the product for a
harness fault, which is why it now asserts the HUD instead of an internal:
  - it matched /print/i, but the control reads "UPG prayer poster" — the product
    names the ARTEFACT, not the action;
  - it patched Document.prototype in the PARENT realm while openPrintFrame writes
    into the iframe's own;
  - openPrintFrame calls doc.open() AFTER append, and open() RESETS the document,
    discarding a write patched at append time.
Through all three the .webp/.html request counters looked corroborating, because
the wall loads both by itself. The HUD is what a user sees, survives a refactor of
the print internals, and cannot silently miss.

The 28 poster sources are committed because the fallback has no input without
them. city-* posters are deliberately NOT covered: they fetch OSM street tiles at
generation time, so they are not reproducible offline and their rasters must stay.
2026-09-24 08:07:22 +00:00
doxa-maps-88655c70
63fad20739 Regions: their worldmap watermark, and two defects it uncovered
Measured first, as the Oswald lesson requires, and this one was real:
their UupgsList root carries worldmap.svg inline (UupgsList.vue:451),
and it genuinely renders on their live country page — HTTP 200, 62,740
bytes over the wire, computed background-size 60%, position 50% 0%,
repeat no-repeat, min-height 400px. Confirmed by screenshotting the
ELEMENT, not the viewport: the watermark sits below the fold, so a page
screenshot shows nothing at all.

public/assets/images/worldmap.svg is their file byte-for-byte — 311KB on
disk, small against this tree's existing assets, which run 644KB to
42MB. The inline background-image takes its base from a new imagesBase
prop, since a dropped bundle cannot assume their absolute /assets path.

generate-tree's staged allowlist needed 'images' added — the same trap
that silently broke the vendored fonts. A dir absent from the allowlist
404s, and an unfetched background-image is quieter than the font
failure was: nothing paints and nothing reports. So the probe asserts
the REQUEST returned 200 rather than that the CSS names the file.

Then, reading the screenshot after every number was already green, two
more defects:

- A CLASS WITHOUT ITS RULE. The search label uses their class name,
  sr-only — the right instinct — but their rule
  (utilities/_visibility.scss:2) was never ported, so the class matched
  nothing and the label rendered as a visible line of text above the
  input, duplicating its own placeholder. Borrowing a class name only
  works if its declarations travel with it, and a class matching no rule
  is invisible to a property diff. Now 1x1px and clipped, still in the
  DOM and still announced.

- A PIXEL CAP WHERE THEIRS IS RELATIVE. .search-box was capped at a
  hardcoded max-width:480px; theirs composes .max-width-md, which is
  max-width:50ch (utilities/_sizes.scss:11). Wrong value and wrong unit —
  ch tracks the input's font, px does not — and ours truncated the
  placeholder mid-word. 50ch now resolves to exactly 600px, their
  measured width.

Regression: 18 suites green, including a new _probe-watermark.mjs at
15/15.
2026-09-24 05:15:06 +00:00
doxa-maps-88655c70
86a3c3bb99 Experimental roundup: FIVE legacy maps resurrected, keyless, in Experimental
Some checks are pending
build-gate / build (push) Waiting to run
The pre-bundler upg-neighbor IIFE holds five lost experimental maps --
send-map, upg-neighbors (cockpit), lang-cluster, bloom-cockpit,
flower-mesh. One wrapper bundle now rescues all five as preset
instances, each mounted in a sandboxed iframe whose host page:
  - loads our self-hosted MapLibre and aliases it as the mapboxgl
    global with a patched Map constructor that rewrites the one
    mapbox:// style to keyless OpenFreeMap Positron -- the whole set
    runs WITHOUT a Mapbox token;
  - shims relative /api fetches to the live API and sets
    MAP_APP_API_URL (the cockpit fails closed without it);
  - passes the legacy profile-config contract (the elements share the
    modern loader lineage).

Wrapper reads profile-config as a PROP (direct-defined element --
inject('profileConfig') is null outside ProfileLoader; the send-map
default masked the miss because send-map is instance #1). The
vocabulary patch rides in the shipped copy ('Lower pressure').

Verified by screenshots read: send-map renders the full 4-D SEND JOIN
app; flower-mesh blooms (India, 2,040 UPGs); the cockpit renders map,
charts and bloom pane. Auto-flows into the SPA's Experimental section
via manifest discovery.
2026-09-24 04:20:24 +00:00
doxa-maps-88655c70
b55044ed95 Vendor Bebas Neue: the last 7 region-page differences were all one font
MEASURED before: the region dashboard matched live doxa.life on 61 of 68
computed properties, and every one of the 7 differences traced to a single
cause — Bebas Neue could not be loaded. Three were the font-family itself, and
four were workarounds it forced: an uppercase hack imitating Bebas's caps, and
two font-sizes stepped down because a wider fallback face overflowed their card
widths. So it was never seven problems, it was one with six knock-ons.

DX-life does not ship the file either — @nuxt/fonts downloads and self-hosts it
for them, which is why their computed family reads "Bebas Neue", "Bebas Neue
Fallback: ...". We could not @font-face from their origin: their file is served
without CORS headers, so a cross-origin face fails silently. The font is a
Google Font under the OFL, so it is vendored here from Google Fonts — both
subsets, their own unicode-ranges (css2 v16) — self-hosted, never a CDN, the
same rule public/assets/vendor/tabulator follows.

library/composables/useDocumentFont.js is shared because @font-face is
DOCUMENT-scoped: a face declared inside a shadow root is ignored, so a custom
element cannot register its own webfont through useShadowStyles. That asymmetry
is the reason this is a separate composable rather than a flag on that one.
Idempotent per document, so a composite registers one face for both panes; both
profiles call it because each has a standalone leaf page where the other is
absent.

With the real face loaded, the workarounds are DELETED rather than kept: the
goal-card heading is back to their 59.6046px, the headline figure back to
59.6046px, and text-transform:uppercase and font-stretch:condensed are gone —
Bebas is already caps and already condensed, and stacking those changed the
glyphs. The three-across switcher still holds at 372/372/372 with no overflow,
which is the geometry that forced the step-downs in the first place.

★ THE CATCH THAT MATTERED. With the font vendored the diff said 68 of 68
IDENTICAL, 0 differ — and it was WRONG. document.fonts reported our face in
"error" status: generate-tree stages an ALLOWLIST of asset dirs into the tree,
fonts/ was not in it, so the leaf resolved assets/fonts/... to a 404 and every
heading silently rendered on the fallback. Both sides were agreeing on a
FALLBACK, which is not a match. A font that 404s is invisible to any check that
reads font-family instead of asking the engine whether the family is usable, so
the probe now calls document.fonts.check() and the staging list carries fonts/.
After the fix: usable true, face "loaded", and the headings render in Bebas.

Two honest notes. The fallback TAIL still differs — theirs carries @nuxt/fonts'
generated metric-fallback names we do not produce — so the probe compares the
FIRST family (the face actually used) and reports the tail as an accepted
difference rather than counting it either way. And document.fonts.check()
returns false on THEIR page, which contradicts it visibly rendering in Bebas,
so that check is trusted in one direction only: it proves ours loaded, it does
not prove parity.

The licence ships beside the font, extensionless: check:public-hygiene refuses
*.txt in a served folder and is right to, while the OFL requires the licence to
accompany redistributed font files. The conventional name satisfies both.
2026-09-24 01:35:48 +00:00
doxa-maps-88655c70
1cf4e89dab Resolve a composite's child bundles against the tree root, not a guessed depth
Some checks are pending
build-gate / build (push) Waiting to run
Driver-reported console text:
  Could not load <doxa-research-map> from ../../doxa-research-map/doxa-research-map.js

A composite declares children as src: '../../<bundle>/<bundle>.js'. That literal
is right from a one-deep leaf and wrong from a two-deep one, and the generator
emits both:
  .../research-map-plus-grid/index.html          ../../ -> /doxa-maps/          200
  .../research-map-plus-grid/default/index.html  ../../ -> /doxa-maps/rmpg/     404

So the path encoded an assumption about the host's depth that the host does not
control and cannot see. It survived because every gate exercised the shallow
leaf, which is genuinely fine.

StackLayout now derives the tree root from the document's own pathname (up to
and including the /doxa-maps/ segment) and rebuilds the path from there --
depth-independent by construction, so a third leaf depth cannot reintroduce it.
Absolute, protocol-relative and root-relative srcs pass through untouched, and
a missing /doxa-maps/ segment returns the original string, so this can only fix
a path, never break a working one.

Measured, both leaves, real browser with live data:
  one-deep  mapEl=true mapH=792 canvasH=856 0 4xx
  two-deep  mapEl=true mapH=792 canvasH=856 0 4xx   (was 404, map absent)

Also carries source that had been left behind by its own built output:
the cities/gospel legend bounding (shipped in 5008a6f as output only) and the
per-profile config carrier in generate-tree.mjs, plus a new fits-screen clause
that wheels inside every column rather than one.
2026-09-23 19:34:28 +00:00
doxa-maps-88655c70
7917cbdd7e Publish composes edges into manifest.json at the bundle level
Some checks are pending
build-gate / build (push) Waiting to run
Bundles that declare composition in their profile meta.json (composes:
[...]) now carry that array on the manifest's bundle object, so a
consumer like the plugin studio can render the composition tree without
re-deriving it. 7 bundles affected: big-picture, dashboard-plus-grid,
research-map-plus-grid, my-upg-100-list, stack-demo, doxa-maps-spa,
plugin-studio. Omitted for bundles with no composed children.
2026-09-23 17:51:17 +00:00
doxa-maps-88655c70
38a7dbb939 Land the swarm's work: no iframes anywhere, a tree that keeps its shape, deeper facets
Some checks are pending
build-gate / build (push) Waiting to run
Checkpointing thirteen files of agent work that were sitting uncommitted while three
agents wrote to a live tree the Driver was watching through hot reload. Uncommitted work
is work at risk, and a tree being edited under an observer oscillates between states that
look like regressions and are really just partial saves.

The second capability-5 shell now mounts its tabs as real elements too, so no iframe
remains in either shell. It was the last holdout: eight tabs, all iframe mode, each
pointing at a hardcoded built-tree leaf that does not exist under the dev server.

The facet rail keeps its shape when a filter narrows it. Selecting one place took the
rail from sixty-six rows to twenty-one — forty-six rows deleted, none dimmed — so the row
a user was about to click vanished under the cursor, which is what "the buttons went
dead" actually was. Rows whose count falls to zero are now re-admitted at their original
slot and depth, dimmed and inert, which also restores the component's own promise to lose
nothing against the tree it replaced.

Expanding a branch and filtering by it are now separate, visible affordances: the whole
row filters, and expand is a real button with its own hit area and label. Rows that were
never clickable are unmistakably inert rather than silently ignored.

Three more facet axes reach the grid — affinity bloc, engagement status and primary
language — each verified to share a field with an existing prop rather than being mapped
on name resemblance. Two derived facets remain unmapped on purpose: they have no matching
row field, and mapping them would filter confidently to the wrong rows.
2026-09-22 14:51:52 +00:00
doxa-maps
3c96917dbe Vendor Vue for the gallery shell, so the self-hosted rung finally exists
Some checks are pending
build-gate / build (push) Waiting to run
generate-gallery.mjs:542 requests assets/vendor/vue/vue.global.prod.js, and
generate-tree.mjs staged only vendor/tabulator -- so that request has 404'd
on every build there has ever been.

It survived because loadVue() degrades in three rungs: self-hosted, then the
jsDelivr CDN, then a plain-DOM render. On any networked machine rung 2
silently covers for rung 1, and the only symptom was a 404 in `bun run
smoke`, a gate nobody was running. Good degradation hid a missing build step.

It mattered anyway: the comment above loadVue promises "self-hosted first
(portable / offline / no-CDN)". Measured in a browser with no outbound
network, the gallery fell all the way to the plain-DOM degrade -- usable, but
not the Vue shell, and the offline claim was false. A tree dropped on a
foreign host or opened over file:// is exactly what that rung is for.

Vendored from node_modules/vue 3.5.42, placed in data/vendor/ (the real
source -- generate-tree rmSyncs public/assets/vendor and re-copies from
data/), tracked in both places exactly as vendor/tabulator already is.

Verified: build exit 0 with zero missing-asset warnings; the gallery now
loads Vue 3.5.42 FROM THE VENDORED COPY (not the CDN), the Vue app mounts,
nav renders 5 rows. `bun run smoke` went from 4 failures to 2.

A correction en route: I first blamed `bun run clean` for deleting the file.
clean.mjs only wipes public/doxa-maps, never public/assets -- the real cause
was generate-tree.mjs:1189 rmSyncing public/assets/vendor and re-copying it
from data/vendor, which is why tabulator persists and a hand-placed file
does not.
2026-09-22 04:59:30 +00:00
doxa-maps
8846be5b47 Let the map classifier see maps nested below a profile root or in another bundle
Some checks are pending
build-gate / build (push) Waiting to run
Two defects in generate-tree.mjs left the only two map-owning composite
leaves marked TOKEN_FREE, so they mounted with no token in profile-config
and survived only on a window.MAP_TOKEN fallback that yields to any
pre-existing global.

- isMapLeaf scans one flat file. research-dashboard.vue has zero engine
  references and is the only .vue in its folder, while the real map lives in
  src/components/dashboards/DashboardMapPanel.vue. New ownsAMapDeeply walks
  src/ for .vue/.js/.mjs and is wired into both the composite path and the
  non-composite branch.
- composes entries dropped the bundle segment, so a child from a sibling
  bundle was looked up in the wrong directory and silently missed.

Both fixes were needed; resolving cross-bundle alone still reports map=false
because the map is a directory deeper.

Negative-tested before wiring in: across every bundle, research-dashboard is
the only profile whose answer changes. Post-build check confirms the seven
genuinely map-free profiles stay token-free. embed suite 40/40, exit 0.
2026-09-22 02:09:59 +00:00
doxa-maps-88655c70
9dc4dda8cf consolidate docs into documentation/ and deployment shells into deployment/
Some checks are pending
build-gate / build (push) Waiting to run
Driver 2026-09-21: 'a folder called deployment which will store the capacitor
folder, wordpress folder and tauri folder', and 'a folder called documentation
to store the contributing folder so docs are in one place'.

docs/ + contributing/ + CONTRIBUTING.md -> documentation/ (three doc homes into
one). capacitor/ + wordpress/ + src-tauri/ -> deployment/.

The public/ -> dist/ rename the Driver asked about is NOT here: after seeing the
measured blast radius (855 tracked files, 54 source refs, and public/ is the
served web-root four consumers point at) the Driver withdrew it - 'keep the
public folder the same'.

Path references were rewritten mechanically (258 across 87 files), but three
classes needed hand fixes because a prefix regex cannot see them:
  - join(ROOT,'docs',...) in 10 internal scripts - a quoted SEGMENT, not a prefix
  - tauri frontendDist '../public' -> '../../public', since src-tauri is now one
    level deeper; check:tauri caught this exactly as the card predicted
  - the WordPress contract test's dirname(__DIR__,2) -> 3, same reason

Also reverted one over-reach: the rewrite renamed a FINDING-CATEGORY label
'docs' in public-hygiene-check.mjs, which is a rule name in its own header, not
a path.

Verified: full build ALL GUARDS PASS, SRI consistent; check:tauri, check:mobile
(cap sync + webDir OK), check:wp-embed, quoted-scripts, capability-citations,
capability-exemplars, library-index, public-hygiene, parse and drop all pass.
2026-09-21 23:23:23 +00:00
doxa-maps-88655c70
ce870b6923 emit assetsBase and the API-base global into the direct-JS embed snippet 2026-09-20 09:32:37 +00:00
doxa-maps-88655c70
38fd121c70 release the dark load backdrop when a leaf's element mounts 2026-09-20 09:16:46 +00:00
doxa-maps-88655c70
0645b78b09 validate @element is a bare tag; warn when an inferred tier contradicts a bundle's complexity 2026-09-20 08:08:01 +00:00
doxa-maps-88655c70
85014aad11 Session work: strobe fix, grid layout, stacked tree legend, selection camera, chip bounding, asset inheritance, gates 2026-09-20 04:49:25 +00:00
doxa-maps-88655c70
ea0c47b277 Brand-neutral shared VerticalTabsLayout with lazy nested-element hosting; fix cap-5 prayer tab leaf path; neutralize library identifiers; add Forgejo CI build-gate workflow
Some checks failed
build-gate / build (push) Has been cancelled
2026-09-19 17:24:30 +00:00
84fe1da1ab Bake profile type into manifest; plugin-studio cap-5 element; UPG-100 print+dots 2026-09-19 06:56:27 +00:00
doxa-maps-88655c70
2d64e21fbf Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) 2026-09-17 18:18:19 +00:00
doxa-maps-88655c70
1a3925c6a6 Rename app-profiles -> plugin-designs; build green under new naming (load-bearing paths updated, guards pass) 2026-09-17 18:18:18 +00:00
swift-river542
9cefd6faa4 Plugin gallery: one generator for the dev server and the build
- internal/playground → internal/plugin-gallery; playground-smoke → plugin-gallery-smoke;
  generate-spa → generate-gallery (scripts: gallery, smoke:gallery; smoke:playground stays as alias)
- generate-gallery exports renderGallery(model, { mode }) — the dev plugin serves the same page
  at / from the discovered registry (dev mode imports the dev entries, ladder popovers on);
  the build writes public/index.html from the manifest (byte-identical to before)
- Docs and probes use the gallery vocabulary
2026-09-16 20:04:18 -05:00
swift-river542
12d1fee133 Public build folder is three entries: index.html, assets/, doxa-maps/
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- index.html is generated by internal/generate-spa.mjs from doxa-maps/manifest.json: one
  self-contained page that lists every bundle, profile and instance, mounts the chosen one,
  and offers Open page / Copy embed; build ends with `bun run spa` (the material shell is no
  longer installed; nav.js, icons.js, languages.js, pane-wrapper/, external/ and _headers
  leave public/)
- The website drop-in follows the same contract: dropin.include in the build config,
  sync-dropin --from-config --prune, public-hygiene --strict fails on any other root entry
- Production bundles no longer carry CSS comments: a build-only Vite plugin strips them from
  every shadow-style block (about 140 per map bundle, 5% smaller); remaining comments reworded
- Share pages and the pin renderer drop their note-style comments
2026-09-16 18:23:14 -05:00
swift-river542
a876e5e97b Host-agnostic share links, token-endpoint chain, dev playground as reusable modules, share/asset gates, validated profile-type vocabulary
- ShareButton resolves its link from where the bundle is hosted (profile-config.shareUrl ->
  embedBaseUrl -> window.MAP_EMBED_BASE_URL -> the bundle's own script URL); profiles pass
  tree-relative embed pages, so links work in the playground, on doxa.life and on any CDN
- Generated leaf pages try token endpoints in order (override -> configured absolute URL ->
  same-origin) and only then offer the paste-once box; build config points at the canonical
  https://doxa.life/api/maps/token
- Dev playground extracted from vite.config.js into internal/playground/{discover,render,dev-plugin}.mjs;
  serves /assets/* like the built site so data-driven maps initialise; passes assetsBase +
  shareUrl to every element; header ladder with a Start-here primer and the five kinds of
  mounting files (single-element, preset-element, multi-view-element, composite-element, app-shell)
- New gates: smoke:playground (every element boots, with status text + data diagnostics),
  smoke:share (Share URL resolves and reopens the same map), sync:dropin (mirror public/ into a
  host checkout, never credentials), deploy:dev (build + sync)
- MapToolbar + ShareButton on every map profile; UPG-100 composite (map + prayer lists) on the
  pane layout; shared --map-radius token consumed by the legend components
- docs/reference.md: profile types table, profile-config assetsBase/shareUrl/embedBaseUrl, gates
2026-09-16 12:10:38 -05:00
doxa-maps
e1d9393227 Every generated embed snippet teaches traffic attribution
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
The one bundler script that generates all the map pages now stamps
?utm_source=YOUR_SOURCE on every copy-paste iframe snippet (profiles AND
instances) and utmSource:"YOUR_SOURCE" in every script snippet — the same
placeholder pattern as YOUR_MAPBOX_TOKEN. A partner replaces it once and every
people-group click-through from their embed attributes to them.
2026-09-16 04:24:32 +00:00
doxa-maps
2f458033b6 MapLibre pages: token-free embeds, no Mapbox peer, no token gate
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
A profile/instance whose config says provider:'maplibre' now generates a page
that loads the MapLibre peer (jsdelivr, same versions as useMapInstance) and
mounts IMMEDIATELY — the token ladder is skipped entirely. Mapbox pages are
unchanged. demo-map (the example vibe coders copy) is now provider-aware via
resolveMapProvider, and ships a 'maplibre' instance as the living token-free
demo. embed-smoke covers instance pages; verified 2/2 with live canvases —
including the maplibre one with NO token anywhere.
2026-09-16 02:53:38 +00:00
doxa-maps
e800f10681 Frame-policy as a real artifact + docs discoverability
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- The build now emits public/_headers implementing the embeddability split
  (direct-embed pages framable anywhere; staging preview-only) — Netlify/CF
  Pages consume it automatically; serving/traefik-embed-headers.yaml is the
  proxy-side reference. The contract is auditable in-repo, not just documented.
- README: docs table gains how-to, layouts/README (the SPA machinery), and
  docs/architecture; Quickstart gains bun run serve + bun run phone.
2026-09-16 02:37:19 +00:00
doxa-maps
5ef3c01c8d Data out of public/ (phase 2): posters + tables live in data/
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
posters/ (42M webp set the poster walls fetch) and tables/ (the merged served
table set — its index.json is the merge; per-profile data/tables remain the
build-time sources) now live in data/ with build copy-steps. Byte parity
verified; served paths unchanged. public/ is now FULLY regenerable — every
source-of-truth folder has a home outside the web-root.
2026-09-16 01:37:52 +00:00
doxa-maps
eca13ac2ff Data out of public/: sources live in data/, the web-root is generated
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
public/ was confusing because three source-of-truth folders lived ONLY inside it.
They now live in data/ (people-groups, vendor, external-bundles) and the build
copies them into the served tree (same pattern as boundaries), so served paths
are unchanged and public/ is fully regenerable. data/README.md states the law.
2026-09-16 01:24:41 +00:00
doxa-maps
994b8d7dcf Prepare repo to the vision: BLUF vision-cast README, library warning, SRI injection, term scrub
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- README rewritten as a BLUF-first vision cast for vibe coders (key-terms glossary,
  origin story, features, requirements, data caveat, developer's note).
- library/components/WARNING.md: prototype in your app-profile; only proven,
  parameterized components graduate into the shared library.
- generate-tree.mjs: inject Subresource Integrity (sha384) + crossorigin into the
  standalone page <script> tags and the copy-paste embed snippets (manifest = registry).
- Scrubbed identity-tied phrasing from comments/docs, keeping the technical rationale
  and the mission content.
2026-09-15 22:58:19 +00:00
doxa-maps
efdf6841e5 feat(security): SRI sha384 per bundle in the manifest (supply-chain integrity)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
generate-tree now hashes each built bundle IIFE and writes integrity: 'sha384-…' into
manifest.json, so the app-shell can load embedded (direct-mounted) plugins with
integrity=… and refuse a tampered/CDN-swapped bundle. Pairs with the mount/locked
flags convert.py adds (DOXA=embedded, foreign=iframe, all locked).
2026-09-15 00:45:41 +00:00
doxa-maps
62b005fe17 perf: unblock SPA load (defer mapbox, preconnect, modulepreload) + preconnect embed pages
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
The SPA loaded mapbox-gl.js + geocoder as render-blocking <script> in <head> (~1.2MB
blocking parse) with no preconnect/preload — the ~10s cold load. Now: defer both (the
app module runs after them in doc order, so window.mapboxgl is still ready at mount),
preconnect api.mapbox.com + pray.doxa.life, and modulepreload the default profile
bundle so mapbox+bundle+API fetch in parallel. Measured headless: time-to-chrome 562ms,
time-to-widget 1068ms (was ~10s). Embed pages get preconnect too (kept mapbox sync there
since they use inline, non-module scripts).
2026-09-15 00:38:52 +00:00
doxa-maps
24b315afdd feat(qa): bake QA corner widget onto generated embed pages (go-base plugin surface)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
generate-tree now honors a qaWidget build flag (default true) and merges qa:true
into each generated embed page's profile-config, so standalone map plugins opened
in go-base show the QA corner widget too — not just the SPA. Partner embeds use the
copy-embed snippet with their own config, so they are unaffected. Opt out with
qaWidget:false in vite.doxa-maps-build-config.json.
2026-09-14 21:50:24 +00:00
doxa-maps
63bc1d77c1 feat(staging): gate profiles out of the generated tree (governance) + clean all-bundles staging
Some checks failed
Deploy to GitHub Pages / build (push) Has been cancelled
Deploy to GitHub Pages / deploy (push) Has been cancelled
Add a gatedProfiles config field (default [key-cities]) honored by the tree
generator: a gated bundle's .js still BUILDS via vite, but it never appears in
the staging root, the per-bundle staging pages, or manifest.json — the surfaces
generate-tree emits. This makes key-cities gated in ONE place for every surface
(convert.py already gates the go-base plugin index), instead of relying on the
box-side converter alone. The staging root now shows all 9 bundles + their
elements with no key-cities leak.
2026-09-13 00:52:24 +00:00
doxa-maps
30a516a6a3 feat: apiBaseUrl as a first-class build-config field — every generated page bakes window.MAP_APP_API_URL (default pray.doxa.life); one knob to hot-swap to the self-hosted campaigns-server on go-base later, no code change
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-12 03:15:36 +00:00
doxa-maps
c7260faaa3 feat: map provider swap — mapbox|maplibre engine + hybrid geocoder + provider badge; staging title doxa-maps 2026-09-11 21:47:25 +00:00
doxa-maps
d5ba21fa76 doxa-maps — portable bundler for embeddable JAMstack maps
Standalone repo that builds embeddable JAMstack map bundles (web components),
composes them into a lazy-loaded SPA via layouts/, and optionally wraps a
Capacitor Android/iOS app — one build, choose your output/CDN. Diátaxis docs.

History reset to a single clean commit; no PII, IPs, or internal terms.
2026-09-10 01:53:07 +00:00