Some checks are pending
build-gate / build (push) Waiting to run
Three docs still told a reader to start a server on every interface: the reference recipe for the share round-trip, the quoted scripts block, and app-wrappers.md. Agents followed those recipes faithfully, which is how twenty publicly bound http.server processes accumulated on this box. All three now bind 127.0.0.1. check:quoted-scripts caught the second one on its own. Also adds the gate-authoring constraint that just broke a build. Plugin and library source is written for Vite, not Node, and fails two ways: the @map alias is unresolvable (hit first), and import.meta.glob is undefined in 18 files including 11 of the 20 plugin index.js. A new check:facet-cascade imported the dashboard config, reached library/colors/_registry.js transitively, and threw -- while already wired into build:unlocked, so the build failed for a reason unrelated to the code being built. The counts in that section were measured, not estimated. A first draft said "every plugin index.js" and the real number is 11 of 20; it also blamed the glob when the first error is actually the alias. States the two rules that follow: build:unlocked is only for gates that run clean in plain Node with no browser and no network, and a gate gets run before it gets wired in -- a broken gate in the build chain is worse than no gate, because it goes red for the wrong reason and trains people to skip the step. Records the other box constraints that have each produced a wrong answer: Node 20 (no fs.glob), bfs find rejecting relative -newermt, no hardware WebGL, and bunx not existing.
93 lines
4.4 KiB
Markdown
93 lines
4.4 KiB
Markdown
# Architecture — app wrappers
|
||
|
||
> The simple overview is in `library/layouts/README.md`. This is the full contract for building and
|
||
> wiring an app wrapper.
|
||
|
||
Each folder in `library/layouts/` is an **app wrapper**: a host shell that composes doxa-maps plugin-designs
|
||
(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 plugin-designs, 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 `kind`s:
|
||
|
||
- `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
|
||
|
||
```bash
|
||
cd library/layouts/app-wrapper-material
|
||
python3 -m http.server 4173 --bind 127.0.0.1
|
||
```
|
||
|
||
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:
|
||
|
||
```js
|
||
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** `library/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).
|