Checkpoint: current working state
Some checks failed
ci / check (push) Has been cancelled

This commit is contained in:
Lumen Stage1 2026-09-29 21:33:33 -05:00
commit e83946c207
57 changed files with 6257 additions and 775 deletions

5
share/README.md Normal file
View file

@ -0,0 +1,5 @@
# public
Static shell entry (index.html no-cache, manifest.webmanifest, fallback.html Ring-0, sw.js precache). ARCH §24.
Stage 1: directory exists with .gitkeep; no feature code yet per IMPLEMENTATION-CONTRACT.md Stage 1. See IMPLEMENTATION-CONTRACT.md §4 responsibilities and §6 B-1…B-7.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

33
share/fallback.html Normal file
View file

@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lumen — offline</title>
<meta name="color-scheme" content="light dark" />
<style>
body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 2rem auto;
padding: 0 1rem;
line-height: 1.5;
}
h1 {
font-size: 1.5rem;
}
.note {
border: 2px solid currentColor;
padding: 1rem;
}
</style>
</head>
<body>
<h1>Lumen</h1>
<p class="note" role="alert">
You are offline and the app shell is not cached. Emergency information is available from
festival staff and printed materials.
</p>
<p>When you are back online, open <a href="/">Lumen</a> to reload the app shell.</p>
</body>
</html>

25
share/index.html Normal file
View file

@ -0,0 +1,25 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#f6f9f7" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0b1411" />
<meta name="description" content="Lumen — offline-first festival companion" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/sol_lunar_icon.svg" type="image/svg+xml" />
<title>Lumen</title>
<script type="module" crossorigin src="/assets/index-CaoEjGwW.js"></script>
<link rel="stylesheet" crossorigin href="/assets/style-D9rb7TfV.css">
</head>
<body>
<div id="app" aria-busy="true">Loading…</div>
<noscript>
<p role="alert">
Lumen requires JavaScript to run the app shell. Emergency information is available on the
printed festival materials.
</p>
</noscript>
</body>
</html>

View file

@ -0,0 +1,30 @@
{
"name": "Lumen",
"short_name": "Lumen",
"description": "Offline-first festival companion — Emergency, Schedule, Map, Festival",
"display": "standalone",
"scope": "/",
"start_url": "/",
"background_color": "#0b1411",
"theme_color": "#f6f9f7",
"icons": [
{
"src": "/sol_lunar_icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}

View file

@ -0,0 +1,16 @@
{
"cacheName": "lumen-shell-0.1.0-fcc18ddc",
"precache": [
"/",
"/index.html",
"/fallback.html",
"/manifest.webmanifest",
"/sol_lunar_icon.svg",
"/icon.png",
"/icon-192.png",
"/icon-512.png",
"/assets/index-CaoEjGwW.js",
"/assets/index-CaoEjGwW.js.map",
"/assets/style-D9rb7TfV.css"
]
}

4
share/sol_lunar_icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

3
share/start.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
python3 -m http.server 8080 --directory .
echo "Open http://localhost:8080 in your browser"

140
share/sw.js Normal file
View file

@ -0,0 +1,140 @@
/**
* Lumen — minimal hand-written service worker (Stage 3)
* Scope: application shell only. No IDB, no dataset, no sync.
* Trace: ARCHITECTURE-DESIGN.md §8, IMPLEMENTATION-CONTRACT.md §16–§18
*/
/* eslint-disable no-restricted-globals -- SW global scope is intentional */
/** Cache version — replaced at build time via Vite define/plugin. Deterministic per shell build. */
const CACHE_NAME = "lumen-shell-0.1.0-fcc18ddc";
/**
* Precache list — replaced at build time with versioned shell list
* (index.html, hashed JS/CSS, manifest, icons, fallback.html).
* Placeholder is replaced by Vite plugin after bundle.
*/
const PRECACHE_URLS = [
"/",
"/index.html",
"/fallback.html",
"/manifest.webmanifest",
"/sol_lunar_icon.svg",
"/icon.png",
"/icon-192.png",
"/icon-512.png",
"/assets/index-CaoEjGwW.js",
"/assets/index-CaoEjGwW.js.map",
"/assets/style-D9rb7TfV.css"
];
self.addEventListener("install", (event) => {
// Safe population: atomically cache all shell URLs; if any fails, install fails
// and the new cache is not treated as valid (existing shell remains usable).
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) => cache.addAll(PRECACHE_URLS))
// Do NOT call skipWaiting here — wait for explicit message or next full start
// per ARCHITECTURE-DESIGN.md:255 (no mid-session clients.claim).
.catch((err) => {
// Ensure failed install does not leave partial cache as valid:
// delete the newly created cache on failure.
caches.delete(CACHE_NAME).finally(() => {
// re-throw to signal install failure to the browser
throw err;
});
}),
);
});
self.addEventListener("activate", (event) => {
// Safe replacement: delete old shell caches only after new shell is installed.
// Old shell remains usable until this runs (next full start).
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(
keys
.filter((k) => k.startsWith("lumen-shell-") && k !== CACHE_NAME)
.map((k) => caches.delete(k)),
),
),
);
});
self.addEventListener("fetch", (event) => {
const req = event.request;
const url = new URL(req.url);
// Only handle same-origin GET
if (req.method !== "GET" || url.origin !== self.location.origin) return;
// Package endpoints are NOT intercepted — app-layer sync controls staging (C-21)
if (url.pathname === "/latest.json" || url.pathname.startsWith("/editions/")) {
return; // let network handle it
}
// Navigation requests — cache-first against shell cache, fallback to network, then fallback.html
const isNavigation =
req.mode === "navigate" ||
req.destination === "document" ||
req.headers.get("accept")?.includes("text/html");
if (isNavigation) {
event.respondWith(
caches.match(req).then((cached) => {
if (cached) return cached;
return fetch(req)
.then((res) => {
// Optional: opportunistically re-fill cache with fresh index.html on navigation fetch
// but do not block response on cache put.
if (res.ok) {
const clone = res.clone();
caches.open(CACHE_NAME).then((c) => c.put(req, clone));
}
return res;
})
.catch(() =>
caches
.match("/fallback.html")
.then((fb) => fb ?? new Response("Offline", { status: 503 })),
);
}),
);
return;
}
// Hashed shell assets (/assets/*) — cache-first (immutable, 1 year per ARCH §24)
if (url.pathname.startsWith("/assets/")) {
event.respondWith(
caches.match(req).then((cached) => {
if (cached) return cached;
return fetch(req).then((res) => {
if (res.ok) {
const clone = res.clone();
caches.open(CACHE_NAME).then((c) => c.put(req, clone));
}
return res;
});
}),
);
return;
}
// Other same-origin GETs (manifest, icons) — cache-first with network fallback
event.respondWith(
caches.match(req).then((cached) => {
if (cached) return cached;
return fetch(req).catch(() => caches.match(req));
}),
);
});
self.addEventListener("message", (event) => {
const data = event.data;
if (typeof data === "object" && data !== null && "type" in data && data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});