Reorder sections so it reads like a journey (indirect, no cheesy labels): the dream
+ the wall you hit → the language (glossary) → what you get → the powers → the honest
decision → the commitment → first steps → the data caveat → docs → the mission.
Same ideas + section content; only the order changed so the most impactful part (the
story) leads.
Lead with the one-liner + the two commands immediately after the tagline; punchier
bullets; condensed the data caveat. Same content, fewer words, essence first.
- 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.
The QA widget's Start now: GET /api/orgs → mint an RS256 gateway token via
/api/o/<slug>/mcp/mint (reads access_token) → POST /sessions to qhoot (Bearer) →
open /qhoot/assembly.html?c=<code>. Verified end-to-end (qhoot 201). Backend side
(qhoot iss/aud aligned to the mint; mint signing key pinned to certified green-key-1)
handled on the app-shell/qhoot infra.
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).
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).
The QA panel now checks the go-base session via same-origin GET /api/auth/me and
shows 'signed in as X' + Start; if not signed in it offers 'Log in with Nuxtinator'
(opens the same-origin /plugin-assets/qa-connect.html popup, which reads the session
and postMessages the identity back). Works when the plugin is same-origin; under
NUXT_PUBLIC_PLUGIN_SANDBOX_ISOLATE_ORIGIN=true the frame is origin-null so the check
is blocked (browser rule) and the button surfaces that. Connect page shipped to the
mfe-index volume root.
The widget mounted but was invisible: as a CHILD component of the custom element,
its <style scoped> block was silently dropped from the shadow root (Vue keeps only
the ROOT component's styles), so position/z-index never applied → it laid out as a
static full-width block. Switch to useShadowStyles (same pattern as legend/table/
map-panel) → the pill now floats bottom-right. Verified headless: absolute,
z-index 2147483000, visible, bottom-right.
The widget rendered but was painted under map legends/panels and could be trapped
by a profile's full-bleed layout. Switch .qa-corner to position:fixed with a
top-of-stack z-index so it floats bottom-right over any map. (Plugin-open path
fixed separately in convert.py: a plugin now opens its representative MAP page,
not the multi-map staging directory — faster load + the widget shows.)
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.
Adds library/components/qa/QaCorner.vue — a bottom-right floating 'QA' button +
panel (OIDC-onboarding shell: 'Log in with Nuxtinator', no key), rendered by
ProfileLoader over any profile. OPT-IN via profile-config.qa (default OFF) so the
same bundles that power partner embeds never show a QA button; the SPA shell sets
qa:true on every mount so it appears on all maps in the power app. Backend (qhoot /
nuxtbase-baas agent bridge + MCP) wired in a later pass — this is the visible shell.
Spec: _qa-cycle/01-ideation-research/qa-loops/qa-embeddable-qa-agent-bridge.md
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.
The section/subgroup headers are non-interactive group labels; their leading
icon added visual weight without meaning. Render them as plain text so the
sidebar reads as clean labelled separators. Nav ITEMS keep their icons, and
the collapsible Experimental folder keeps its icon+chevron (interactive row).
Rail mode already hides .sect-hdr entirely, so no empty headers there.
- SPA sidebar: suppress the leading section divider on the FIRST nav group.
Every section emitted a `.sep` rule before its header, so Prayer Mobilization
(the first section) drew a 1px line directly under the header with nothing
above it to divide. drawSide() now tracks a firstGroup flag and skips it.
- Research dashboard: when the table under the map collapses, resize the map to
fill the freed space. The ResizeObserver alone missed the grid reflow at the
instant the table cell flips to display:none (v-show), leaving the map canvas
at its old size (gray gap). DashboardMapPanel now also resizes on the
'toggleTable' bus event (rAF + a 280ms settle pass), with clean teardown.
Three fixes for the power-app SPA running as a go-base plugin (sandboxed
iframe, origin 'null' — where localStorage throws and root-absolute asset
paths misresolve under the /plugin-assets/<name>/ subpath):
- localStorage: wrap every theme/preference read+write in try/catch across
the library (ProfileLoader, useMapTheme, uiStore). `typeof localStorage`
is not enough — in a sandbox the object exists but ACCESS throws
SecurityError. Sandboxed maps now boot themeless instead of crashing.
- assets: the SPA shell now injects an absolute assetsBase (derived from its
own document.baseURI) into every mounted element's profile-config, so
bundles fetch tables/vendor (Tabulator) from the correct subpath instead
of a root-absolute /assets that 404s under /plugin-assets/doxa-maps-spa/.
- nav: revert the Prayer Mobilization section icon to 'heart' (the prayer
glyph read as a header it is not).
Rebuilds every bundle (library-wide localStorage change).
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.