doxa-maps/docs/architecture/app-wrappers.md

4.4 KiB
Raw Blame History

Architecture — app wrappers

The simple overview is in layouts/README.md. This is the full contract for building and wiring an app wrapper.

Each folder in layouts/ is an app wrapper: a host shell that composes doxa-maps app-profiles (the independently-built custom elements) into one application.

Terminology (deliberate — don't drift)

Thing What it is
app-profile An independently-built, self-contained custom element (a map, a dashboard, a list). These are the micro-frontends.
app wrapper (this folder) The host/shell that mounts app-profiles, provides nav/chrome/routing, and bounds resources. Not itself a micro-frontend — it's the container.

A wrapper is a host page, not a regular app-profile. Keeping it a host (rather than a bundle nested inside a bundle) avoids the relative-path/base-URL resolution problem that nesting one position-independent bundle inside another would create.

Wrappers today

  • app-wrapper-material/ — Material-styled wrapper. Structure/behaviour modelled on the Quasar YouTube layout gallery; conventional theming + nav model (--ui-* CSS custom properties, a SidebarNavItem-shaped tree). Two-mode sidebar, self-hosted icons.
  • pane-wrapper/ — a second wrapper beside it.

The sidebar contract (nav.js)

NAV is an ordered list. Order = render order, top to bottom. Three kinds:

  • item — a single entry. At top level these are loose items in no group.
  • section — an always-open labelled group with an icon + name (the YouTube "guide" look).
  • folder — a collapsible group (open: false by default).

Every entry carries target: 'both' | 'desktop' | 'mobile'.

navForTarget(NAV, target) filters the tree, and is used by both:

  1. the UI — hides non-matching entries, and
  2. the packager — the mobile/Capacitor build copies fewer bundles into webDir.

That's why a Desktop only folder both disappears on phones and is excluded from the app binary.

Sidebar modes (intentionally different per platform)

Collapsed Expanded
Desktop (≥1024px) icon rail stays visible (labels hidden) full width: icons + labels + section headers
Mobile (<1024px) completely hidden (saves space) overlay drawer + scrim; choosing a profile closes it

Icons (icons.js)

icons.js ships inline SVG — no CDN, no icon font, no Google/YouTube request. This is a hard requirement, not a preference: F-Droid builds from source and rejects proprietary blobs / non-free CDN dependencies, and local icons also mean the wrapper works offline and inside Capacitor from local assets. Paths follow the Lucide idiom (24×24, 2px stroke, round caps).

Local staging

cd layouts/app-wrapper-material
python3 -m http.server 4173 --bind 0.0.0.0

Then open it and narrow the window below 1024px (or use the browser device toolbar) to check the mobile behaviour.

The PROFILES registry (the wrapper's map catalog)

Beside nav.js, the wrapper's index.html declares PROFILES — one entry per mountable map:

const PROFILES = {
  'prayer-progress': {
    tag: 'doxa-map',                                      // the element to mount (a bundle may register several)
    src: './doxa-maps/doxa-simple-map/doxa-simple-map.js', // the bundle IIFE, tree-relative
    views: ['map'],                                        // which sub-views (map/list/tree) this profile offers
    // + the profile-config the wrapper passes (copied from the generated instance page's BASE_CONFIG)
  },
}

nav.js ids route to PROFILES keys; the wrapper lazy-loads src on first visit and mounts tag. Gotchas: an instance page's tag may differ from the bundle name (doxa-simple-map registers <doxa-map> too), and colorStrategy values are registry keys derived from FILENAMES (prayer-progress.js → 'prayerProgress').

How a wrapper becomes the served SPA

bun run shell (internal/install-shell.mjs) copies layouts/<wrapper>/ to the root of public/, so public/index.html is the shell and public/doxa-maps/… are the bundles it mounts — one served tree, two roles. Copy (not symlink) keeps Railway/Capacitor/file:// happy; --no-token omits the local-only token.local.js (mandatory for release builds). Capacitor also requires this root index.html (cap copy hard-fails without it).