@flyos/boot-overlay
v1.1.0
Published
Canonical FlyOS pre-bootstrap boot region for index.html — Native Federation stale-cache self-heal plus a three-state (waiting/stuck/failed) boot overlay — with a sync CLI that writes it into an app's index.html and verifies it in CI.
Downloads
436
Maintainers
Readme
@flyos/boot-overlay
The canonical FlyOS pre-bootstrap region for an app's index.html, plus a CLI that writes it
into your file and verifies it in CI.
It carries two things that must exist before the first module loads:
- Native Federation stale-cache self-heal (
window.__flyNfCacheHeal) — NF hashes shared singleton filenames by version, not content, so after a redeploy animmutable-cached_angular_core.<hash>.jsimports a now-deleted@nf-internal/chunk-*and bootstrap dies. Only a{cache:'reload'}re-fetch defeats an already-cached entry, and the bundle that failed to import is exactly where a post-bootstrap handler would have lived. The same version-hashed filename also produces a second, quieter symptom: a cached bundle that predates the app chunk importing it resolves and fetches fine and merely lacks a symbol —does not provide an export named 'X'.isCacheErrormatches that wording too (Chrome, Safari and Firefox each phrase it differently). - A three-state boot overlay (
window.__flyBoot) covering polyfills →initFederation→ shared bundles → theAPP_INITIALIZERchain → blocking initial navigation.
Why a generated region instead of an import
This code cannot be a module: it exists to cover the wait for the modules, and to recover from an
import that already failed. It has to be in the HTML the browser already has. What can be
centralised is authorship — so the text lives here, each repo carries a generated copy between
markers, and --check turns silent drift into a loud CI failure.
That drift was real, not hypothetical: before this package the same "PLATFORM CONTRACT — do not drop" block existed in four repos at three different revisions.
Install
npm i -D @flyos/boot-overlayAdd the markers to index.html — the CLI fills everything between them:
<head>
<!-- fly-boot:head:start -->
<!-- fly-boot:head:end -->
</head>
<body>
<!-- fly-boot:body:start -->
<!-- fly-boot:body:end -->
<fly-root ngCspNonce="__CSP_NONCE__"></fly-root>
</body>npx fly-boot-sync src/index.html # write
npx fly-boot-sync --check src/index.html # CI: exit 1 when staleA file with no markers is an error, never a silent skip.
Wire the teardown — required
Nothing removes the overlay on its own. On your root component (not your authenticated shell — public routes must clear it too):
afterNextRender(() => window.__flyBoot?.done());Forgetting it is survivable but not free: at 15s the watchdog checks whether <fly-root> has
children and tears itself down if the app actually rendered, rather than covering a live UI for ever.
API
| Call | Effect |
|------|--------|
| done() | Fade and remove the node (not just hide it — a transparent full-screen layer swallows clicks). |
| stuck() | Swap the spinner for "taking longer than usual" + Reload. |
| fail(detail) | The same, plus the technical detail in a <pre>. |
window.onerror / unhandledrejection handlers are included and call maybeHeal first, staying
silent when a heal reload is already in flight so the overlay covers the reload.
Behaviour worth knowing
- 250ms delayed reveal. A boot faster than that paints nothing — a spinner glimpsed for 150ms reads as slower than a brief blank.
- 15s watchdog. A splash with no way out is worse than the white screen it replaces: a dead bootstrap would spin for ever and read as "still working".
- Theme before first paint from
localStorage['user_settings'].theme. Apps that persist nothing get light, the platform default. Hardcoding a background reintroduces the white→dark flash for dark-mode users, because every app applies its persisted theme from anAPP_INITIALIZER— the very end of the gap this covers. - Locale from
user_settings.language, then<html lang>, thenen.ar/urrender RTL. - The Reload button ships with static
Reloadtext, which the script immediately replaces with the localised label. Nothing paints it —.fly-boot__panelisdisplay:noneuntil JS setsdata-state— but an empty<button>is an error under@angular-eslint/template/elements-content, and every consumer lintsindex.htmlas an Angular template via the**/*.htmlblock in@flyos/code-style. Deliberately not anaria-label: that would leave an English accessible name over an Arabic/French/Urdu visible one (WCAG 2.5.3). - The heal is a prod fix; in dev it costs one reload.
ng servebundles a shared singleton once at server start and never regenerates it on a watch rebuild, so adding an export to a shared package makes the app chunk import a symbol the served bundle lacks — the same wording the heal now matches. There the staleness is server-side, so the reload cannot fix it: expect one wasted reload (the 10-minute guard blocks a loop), then the failure panel. Restart the dev server. - CSP. Both blocks carry
nonce="__CSP_NONCE__", which the platform nginx rewrites per request. Dropping the nonce is invisible in dev (no CSP) and fatal in prod. - Embedded mode. A federated remote's
index.htmlis never loaded — the host document governs — so this is standalone-only in effect. In-shell, the equivalent wait is covered by the shell's own<fly-window-loading>.
