This commit is contained in:
parent
fcc18ddcc8
commit
e83946c207
57 changed files with 6257 additions and 775 deletions
5
share/README.md
Normal file
5
share/README.md
Normal 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.
|
||||
2
share/assets/index-CaoEjGwW.js
Normal file
2
share/assets/index-CaoEjGwW.js
Normal file
File diff suppressed because one or more lines are too long
1
share/assets/index-CaoEjGwW.js.map
Normal file
1
share/assets/index-CaoEjGwW.js.map
Normal file
File diff suppressed because one or more lines are too long
1
share/assets/style-D9rb7TfV.css
Normal file
1
share/assets/style-D9rb7TfV.css
Normal file
File diff suppressed because one or more lines are too long
33
share/fallback.html
Normal file
33
share/fallback.html
Normal 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
25
share/index.html
Normal 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>
|
||||
30
share/manifest.webmanifest
Normal file
30
share/manifest.webmanifest
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
share/precache-manifest.json
Normal file
16
share/precache-manifest.json
Normal 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
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
3
share/start.sh
Executable 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
140
share/sw.js
Normal 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();
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue