feat: Als PWA installierbar — Manifest, Offline-Worker (network-first), Dateihandling (D73)
Manifest mit Icons aus der Marke und Standalone-Fenster; ein bewusst dummer Service Worker beantwortet nur die App-Navigation network-first und hält die zuletzt gesehene Fassung für den Offline-Fall — die Update-Prüfung (D45) bleibt dadurch unverändert wahr, der skipWaiting-Lebenszyklus entfällt. Die installierte App registriert sich für .werkbaum-Dateien (file_handlers + launchQueue -> adoptFile). Beide Deploy-Wege kopieren die App-Hülle mit; .htaccess liefert .webmanifest mit MIME-Typ aus. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f6785e3138
commit
19053eba1d
@@ -88,6 +88,11 @@ jobs:
|
||||
cp frontend/dist/llms.md site/llms.md
|
||||
# Wegweiser der llms.txt-Konvention (D43-Nachtrag 2)
|
||||
cp frontend/dist/llms.txt site/llms.txt
|
||||
# PWA-Hülle (D73): Manifest + Icons + Service Worker — public/-Assets
|
||||
# neben der einen Datei
|
||||
cp frontend/dist/manifest.webmanifest site/manifest.webmanifest
|
||||
cp frontend/dist/icon-192.png frontend/dist/icon-512.png frontend/dist/icon-maskable-512.png site/
|
||||
cp frontend/dist/sw.js site/sw.js
|
||||
|
||||
- name: Pages-Konfiguration
|
||||
uses: actions/configure-pages@v6
|
||||
|
||||
@@ -19,6 +19,9 @@ reverse.
|
||||
|
||||
## 2026-08-25
|
||||
|
||||
- The installed app registers for `.werkbaum` files — a double-click in the file manager opens them straight into the editor
|
||||
- A service worker starts the editor offline; navigations stay network-first, so the update notice keeps telling the truth
|
||||
- Werkbaum installs as an app: a web app manifest with standalone display and icons rendered from the brand mark
|
||||
- PWA file handling recorded in the plan — an installed app would open `.werkbaum` files on double-click and save without any dialog
|
||||
- In Chromium browsers, saving writes back to the opened file, and the same file reopens into the same document
|
||||
- Open a local `.werkbaum` file and save the document back as a file, from the document menu
|
||||
|
||||
@@ -5389,3 +5389,90 @@ IndexedDB-Rundreise über einen Neustart: Stub-Handles überleben den
|
||||
Structured Clone nicht (`DataCloneError`, planmäßig geschluckt) — echte
|
||||
Handles sind gerade dafür klonbar; dieser eine Pfad ist Code-Review statt
|
||||
Messung. 480 Tests.
|
||||
|
||||
## D73 — PWA: Manifest und ein bewusst dummer Offline-Worker, network-first
|
||||
Werkbaum ist als App installierbar (`#bld.pwa`): Manifest mit Icons und
|
||||
Standalone-Fenster, ein Service Worker für den Offline-Start, und die
|
||||
installierte App registriert sich für `.werkbaum`-Dateien. Die tragende
|
||||
Entscheidung ist die Rolle des Workers — und sie hat den gefürchteten Teil
|
||||
des Features aufgelöst.
|
||||
|
||||
**Der Worker ist ein Offline-Mantel, kein App-Verwalter.** Er fasst
|
||||
ausschließlich die **Navigation zur App-Wurzel** an und beantwortet sie
|
||||
**network-first**: Der Server bleibt die Quelle der Wahrheit, genau wie ohne
|
||||
Worker; der Cache hält nur die zuletzt gesehene Fassung der einen
|
||||
self-contained Datei (D19) für den Offline-Fall und wird bei jeder
|
||||
erfolgreichen Navigation aufgefrischt. Alles andere — `?sourceUrl=`- und
|
||||
Pad-Abrufe (D23/D31), `llms.md`/`llms.txt`, jeder `fetch()` — geht unangefasst
|
||||
durch.
|
||||
|
||||
**Damit blieb die geplante D45-Migration aus — und das ist ein Befund, keine
|
||||
Abkürzung.** Der Plan-Knoten hieß „The reload notice moves into the worker",
|
||||
in der Annahme, ein Worker entscheide, was ausgeliefert wird, und der
|
||||
Vergleich „laufender Build gegen den, den der Server sendet" (D45) verliere
|
||||
seine Grundlage. Das gilt für einen **cache-first** Worker — und genau
|
||||
deshalb ist der verworfen: Er hätte den skipWaiting-/updatefound-Lebenszyklus
|
||||
gebraucht (die Stelle, an der PWAs erfahrungsgemäß Fehler sammeln, und D45
|
||||
hat seine eigene Fehlergeschichte), eine zweite Update-Logik neben der
|
||||
bestehenden, und jeden Nutzer bis dahin auf der zuerst installierten Fassung
|
||||
festgenagelt. Network-first braucht nichts davon: Der Prüf-`fetch()` ist
|
||||
keine Navigation und läuft ans echte Netz; „Jetzt laden" ist eine Navigation
|
||||
und bekommt die frische Fassung. Beides gemessen, nicht angenommen (unten).
|
||||
Der Knoten heißt jetzt „The reload notice stays truthful under the worker" —
|
||||
die Arbeit war der Nachweis, nicht der Umzug. Der Preis von network-first ist
|
||||
benannt: Der Start kostet online weiterhin einen Netz-Abruf (wie bisher auch)
|
||||
statt sofort aus dem Cache zu kommen — für ein Produkt, das laufend deployt,
|
||||
der richtige Tausch.
|
||||
|
||||
**`sw.js` ändert sich praktisch nie.** Weil die App vom Server kommt und
|
||||
nicht aus dem Worker, gibt es keine Versionsnummer, die dort gepflegt oder
|
||||
von den Deploy-Skripten eingespritzt werden müsste — kein zweiter
|
||||
`sed`-Stempel neben der Footer-Version (D16).
|
||||
|
||||
**Aus „eine Datei" wird ehrlich „eine Datei plus App-Hülle".** Manifest,
|
||||
Icons und Worker sind **nicht inlinebar** — der Browser holt sie per URL;
|
||||
ein Worker braucht seine eigene Adresse. Sie liegen als `frontend/public/`-
|
||||
Assets neben der Datei (der `llms.md`-Weg, D43) und werden von **beiden**
|
||||
Deploy-Wegen mitkopiert (Pages-Workflow und `deploy-prod.sh` stellen die Site
|
||||
je von Hand zusammen — dieselbe Doppelpflege wie bei den `sed`-Regeln, D16).
|
||||
Die `file://`-Tauglichkeit der einen Datei bleibt: Ohne die Hülle fehlt nur
|
||||
die Installierbarkeit, nicht die App. Die `.htaccess` bekommt den MIME-Typ
|
||||
für `.webmanifest` — dieselbe Apache-Falle wie bei `.md` (D43-Nachtrag 2):
|
||||
unbekannte Endung, kein Content-Type, Chromium verwirft das Manifest still.
|
||||
|
||||
**Icons aus der Marke, eingecheckt.** Die Raster-Größen (192/512 plus eine
|
||||
Maskable-Variante mit Schutzzone: Marke auf 60 % statt 78 % der Fläche) sind
|
||||
einmalig per Inkscape aus `docs/brand/favicon.svg` gerendert und eingecheckt —
|
||||
der Fonts-Präzedenzfall (D20): Assets im Repo, kein Werkzeug im Build.
|
||||
|
||||
**Nicht im Dev-Server registriert.** Dort würde der Worker die HMR-Seite
|
||||
cachen; der Zweig hängt an `!import.meta.env.DEV` und fällt im Dev als toter
|
||||
Code weg. Auf `file://` und http ohne Secure Context gibt es keinen nutzbaren
|
||||
`serviceWorker` — das Scheitern ist geschluckt, die App läuft ohne.
|
||||
|
||||
**Dateihandling (`#bld.pwa.files`):** `file_handlers` im Manifest
|
||||
(`.werkbaum`/`.txt`, dieselben Endungen wie `FILE_TYPES`, D72) plus ein
|
||||
`launchQueue`-Empfänger, der das gereichte Handle an das vorhandene
|
||||
`adoptFile()` gibt — dieselbe Datei landet damit im selben Dokument, und das
|
||||
gemerkte Handle macht „Als Datei speichern" dialogfrei (D72-Nachtrag).
|
||||
`launch_handler: focus-existing`, damit der Doppelklick ein offenes Fenster
|
||||
wiederverwendet, statt Instanzen zu stapeln. Chromium only — Firefox
|
||||
installiert auf dem Desktop nicht, Safari kennt `file_handlers` nicht; dort
|
||||
ändert sich nichts.
|
||||
|
||||
**Nachgemessen** am gebauten Stand (`vite preview`, dist auf localhost:8138):
|
||||
Worker aktiv und `controller` gesetzt, Cache hält genau `./`. Dann ein Marker
|
||||
in `dist/index.html` geschrieben: Der D45-artige `fetch(…, no-store)` **sieht
|
||||
ihn sofort** (läuft also am Worker vorbei ans Netz), während die laufende
|
||||
Seite ihn nicht hat; ein Reload **lädt ihn** (Navigation network-first) und
|
||||
frischt den Cache mit auf. Server gestoppt, Reload: Die Seite kommt
|
||||
vollständig aus dem Cache (18 Knoten gerendert, Marker enthalten). Manifest
|
||||
parst mit `file_handlers` und `launch_handler`; `launchQueue` existiert und
|
||||
der Consumer registriert sich fehlerfrei. Aufgeräumt per `unregister()` +
|
||||
`caches.delete()`. 480 Tests unverändert grün.
|
||||
|
||||
**Werkzeuggrenzen, wie bei D72 benannt:** Installieren, der OS-Doppelklick
|
||||
auf eine `.werkbaum`-Datei und die persistente Schreibberechtigung der
|
||||
installierten App sind Betriebssystem-Dialoge und bleiben ein Handtest auf
|
||||
echter Hardware; gemessen ist alles bis an diese Kante (Manifest gültig,
|
||||
Consumer registriert, `adoptFile()`-Weg seit D72 geprüft).
|
||||
|
||||
@@ -99,11 +99,11 @@
|
||||
- [^] #bld.tests: Unit tests (S)
|
||||
- [^] #bld.pages: Published on every push (S)
|
||||
- [^] #bld.update: Says when a new build is out (S) %% compares against the running one, see D45
|
||||
- [ ] #bld.pwa: Installable as a progressive web app (M)
|
||||
- [ ] #bld.pwa.manifest: A manifest with name, icons and standalone display (XS)
|
||||
- [ ] #bld.pwa.sw: A service worker that serves the app offline (S) %% one file to cache
|
||||
- [ ] #bld.pwa.update: The reload notice moves into the worker (S) %% see D45
|
||||
- [ ] #bld.pwa.files: Double-click a .werkbaum file, save without any dialog (S) :#ed.files.inplace %% file_handlers + launchQueue
|
||||
- [x] #bld.pwa: Installable as a progressive web app (M)
|
||||
- [x] #bld.pwa.manifest: A manifest with name, icons and standalone display (XS)
|
||||
- [x] #bld.pwa.sw: A service worker that serves the app offline (S) %% network-first, cache as fallback
|
||||
- [x] #bld.pwa.update: The reload notice stays truthful under the worker (S) %% see D45/D73
|
||||
- [x] #bld.pwa.files: Double-click a .werkbaum file, save without any dialog (S) :#ed.files.inplace %% file_handlers + launchQueue
|
||||
- [-] #bld.ghpages: Deploy through a gh-pages branch (S) %% GitHub Actions instead
|
||||
- [-] #bld.gfonts: Fonts from Google (XS) %% self-hosted, no third-party request
|
||||
- [ ] #be: Backend (XL)
|
||||
@@ -667,31 +667,32 @@
|
||||
|
||||
#bld.pwa
|
||||
Installable from the browser and startable like an app, in its own window
|
||||
and with its own icon. Everything it needs is already in one file, so what
|
||||
is missing is mostly the declaration that says so.
|
||||
and with its own icon. Everything it needs was already in one file — what
|
||||
was missing was mostly the declaration that says so.
|
||||
|
||||
#bld.pwa.manifest
|
||||
Name, icons, theme colour and a standalone display mode — the declaration a
|
||||
browser reads before it offers to install anything. The brand mark exists
|
||||
and needs the raster sizes an installer asks for.
|
||||
browser reads before it offers to install anything. The raster icons are
|
||||
rendered from the brand mark, with a padded maskable variant.
|
||||
|
||||
#bld.pwa.sw
|
||||
A worker answering from the cache lets the editor start without a network.
|
||||
There is exactly one object to cache, because the build inlines fonts,
|
||||
styles and modules; documents live in the browser anyway. Texts pulled from
|
||||
a URL or a pad stay online by their nature.
|
||||
A worker lets the editor start without a network. It is deliberately dumb:
|
||||
navigations go network-first, and the cache only keeps the last version it
|
||||
has seen of the one self-contained file for the offline case. Texts pulled from a URL
|
||||
or a pad pass through untouched and stay online by their nature.
|
||||
|
||||
#bld.pwa.update
|
||||
With a worker in front of the page, the reload notice has to come from it:
|
||||
it decides what gets served, so "the running build against the one the
|
||||
server sends" is no longer the same comparison.
|
||||
The feared migration turned out unnecessary: because the worker only
|
||||
touches navigations and answers them network-first, the update check still
|
||||
fetches the real server and a reload really loads the new build. The work
|
||||
here was proving that, not moving code.
|
||||
|
||||
#bld.pwa.files
|
||||
The installed app registers for the .werkbaum extension: double-clicking a
|
||||
file in the OS opens it here, with a ready file handle from the launch
|
||||
queue. Together with the persistent write permission an installed app can
|
||||
hold, editing becomes open, type, save — no dialog at all. The saving
|
||||
mechanics already exist; what is missing is the manifest entry and the
|
||||
mechanics existed already; this added the manifest entry and the
|
||||
launch-queue receiver. Chromium only.
|
||||
|
||||
#bld.ghpages
|
||||
|
||||
@@ -554,6 +554,19 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
|
||||
Hash-Rückfall, der Marker-Pfad ist nur zu prüfen, wenn man einen echten
|
||||
`commit/<sha>` in `index.html` einspritzt (danach zurücknehmen!) und
|
||||
`window.fetch` überschreibt.
|
||||
- **PWA (D73):** `public/sw.js` fasst NUR die Navigation zur App-Wurzel an
|
||||
und beantwortet sie **network-first** — der Cache ist reiner
|
||||
Offline-Rückfall. Wer daraus cache-first macht, bricht die Update-Prüfung
|
||||
(D45): deren `fetch()` läuft nur deshalb ans echte Netz, und „Jetzt laden"
|
||||
bekommt nur deshalb die frische Fassung. Keine Registrierung im Dev-Server
|
||||
(`!import.meta.env.DEV` — sonst cacht der Worker die HMR-Seite). Manifest,
|
||||
Icons und `sw.js` sind public/-Assets neben der einen Datei; **jedes neue
|
||||
public/-Asset muss in BEIDE Deploy-Wege** (pages.yml und deploy-prod.sh
|
||||
stellen die Site je von Hand zusammen) und ggf. in `scripts/prod.htaccess`
|
||||
(Apache kennt `.webmanifest` nicht, D43-Falle). Der `launchQueue`-Empfänger
|
||||
(Dateidoppelklick der installierten App) reicht Handles an `adoptFile()`
|
||||
(D72) weiter — Installieren/Doppelklick sind nur auf echter Hardware
|
||||
prüfbar.
|
||||
- **Neuigkeiten (D58):** `NEWS` kommt aus dem virtuellen Modul
|
||||
`virtual:werkbaum-news` (Vite-Plugin in `vite.config.js`), gefüllt zur
|
||||
**Bauzeit** aus `docs/CHANGELOG.md` (die Notizen) und der git-Historie des
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
<title>Werkbaum – PSP & Lean Pathfinding Editor (Work Breakdown Structure)</title>
|
||||
<meta name="description" content="Werkbaum ist ein textbasierter Editor für PSP/Lean Pathfinding und Projektstrukturpläne (WBS): Schreibe die Struktur als Text, das And/Or-Diagramm entsteht live. Auch für Feature-Trees & Requirements.">
|
||||
<link rel="icon" type="image/svg+xml" href="../docs/brand/favicon.svg">
|
||||
<!-- PWA (D73): Manifest + Icons liegen als public/-Assets NEBEN der einen
|
||||
self-contained Datei (nicht inlinebar — der Browser holt das Manifest
|
||||
per URL). Relativer Pfad, damit Pages unter /werkbaum/ und die
|
||||
Wurzel-Installation denselben Verweis nutzen; per file:// bleibt der
|
||||
Link wirkungslos, die Datei selbst läuft unverändert. -->
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<meta name="theme-color" content="#243447">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Werkbaum",
|
||||
"short_name": "Werkbaum",
|
||||
"description": "A textual notation for work breakdown structures with and/or decomposition, and a live diagram editor.",
|
||||
"start_url": "./",
|
||||
"scope": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#243447",
|
||||
"icons": [
|
||||
{ "src": "icon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "icon-512.png", "sizes": "512x512", "type": "image/png" },
|
||||
{ "src": "icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
||||
],
|
||||
"file_handlers": [
|
||||
{ "action": "./", "accept": { "text/plain": [".werkbaum", ".txt"] } }
|
||||
],
|
||||
"launch_handler": { "client_mode": "focus-existing" }
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Werkbaum — Service Worker (D73).
|
||||
*
|
||||
* Bewusst ein dummer Offline-Mantel, kein App-Verwalter: Navigationen gehen
|
||||
* NETWORK-FIRST (der Server ist die Quelle der Wahrheit, wie ohne Worker),
|
||||
* der Cache hält nur die zuletzt gesehene Fassung der einen self-contained
|
||||
* Datei (D19) für den Offline-Fall bereit. Drei Folgen, alle Absicht:
|
||||
*
|
||||
* - Die Update-Prüfung (D45) bleibt wahr: ihr fetch() ist keine Navigation
|
||||
* und läuft unangefasst ans Netz; „Jetzt laden" ist eine Navigation und
|
||||
* bekommt network-first die frische Fassung. Kein skipWaiting-Tanz,
|
||||
* keine zweite Update-Logik.
|
||||
* - ?sourceUrl=- und Pad-Abrufe (D23/D31) werden nie abgefangen — der
|
||||
* Worker fasst ausschließlich die Navigation zur App-Wurzel an; llms.md,
|
||||
* llms.txt und alles Fremde gehen unverändert durch.
|
||||
* - Diese Datei ändert sich praktisch nie: Die App kommt vom Server, nicht
|
||||
* aus dem Worker — es gibt keine Versionsnummer, die hier gepflegt werden
|
||||
* müsste.
|
||||
*/
|
||||
const CACHE = 'werkbaum-shell';
|
||||
|
||||
self.addEventListener('install', e => {
|
||||
/* Die Shell sofort vorhalten, damit Offline schon nach dem ersten Besuch
|
||||
funktioniert (die erste Navigation lief noch ohne Worker). {cache:
|
||||
'reload'} umgeht den HTTP-Cache — vorgehalten wird, was der Server
|
||||
JETZT sagt. Scheitert das (Installation offline), bleibt der Cache
|
||||
leer und die nächste erfolgreiche Navigation füllt ihn. */
|
||||
e.waitUntil(
|
||||
caches.open(CACHE)
|
||||
.then(c => fetch('./', { cache: 'reload' })
|
||||
.then(r => { if (r.ok) return c.put('./', r); }))
|
||||
.catch(() => {})
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
e.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', e => {
|
||||
/* Nur die Navigation zur App selbst; alles andere geht unangefasst durch. */
|
||||
if (e.request.mode !== 'navigate') return;
|
||||
const path = new URL(e.request.url).pathname;
|
||||
const scopePath = new URL(self.registration.scope).pathname;
|
||||
if (path !== scopePath && path !== scopePath + 'index.html') return;
|
||||
|
||||
e.respondWith((async () => {
|
||||
try {
|
||||
const r = await fetch(e.request);
|
||||
if (r.ok) {
|
||||
const copy = r.clone();
|
||||
e.waitUntil(caches.open(CACHE).then(c => c.put('./', copy)));
|
||||
}
|
||||
return r;
|
||||
} catch (_) {
|
||||
const m = await caches.match('./');
|
||||
return m || Response.error();
|
||||
}
|
||||
})());
|
||||
});
|
||||
@@ -4568,9 +4568,40 @@ try{ startLang = localStorage.getItem('werkbaum-lang') || detectLang(); }catch(_
|
||||
applyLang(I18N[startLang] ? startLang : 'de'); /* setzt Texte + rendert */
|
||||
initDocs(); /* Dokumente laden + aktiven Text in den Editor (nach Sprache) */
|
||||
if(hasFsAccess) idbLoadHandles(); /* gemerkte Datei-Handles zurückholen (D72, Stufe 2) */
|
||||
|
||||
/* PWA-Dateihandling (D73): Die installierte App registriert sich über das
|
||||
Manifest (`file_handlers`) für .werkbaum/.txt; ein Doppelklick im
|
||||
Dateimanager reicht die Datei als Handle über die launchQueue herein.
|
||||
`adoptFile()` (D72) übernimmt — dieselbe Datei landet im selben Dokument,
|
||||
und das Handle macht „Als Datei speichern" dialogfrei. Außerhalb einer
|
||||
installierten Chromium-App gibt es die launchQueue nicht: dann ist der
|
||||
Block ein No-op. */
|
||||
if('launchQueue' in window){
|
||||
window.launchQueue.setConsumer(async params => {
|
||||
for(const h of (params && params.files) || []){
|
||||
try{
|
||||
const text = await (await h.getFile()).text();
|
||||
await adoptFile(h, h.name, text);
|
||||
}catch(_){ /* nicht lesbar — still, wie beim Picker-Abbruch (D72) */ }
|
||||
}
|
||||
});
|
||||
}
|
||||
applyMobile(); /* Mobil-Verhalten (nach Sprache/Restore) anwenden */
|
||||
loadRemoteSource(); /* ?sourceUrl= / ?etherpad= nachladen (asynchron, D23/D31) */
|
||||
|
||||
/* ---------- PWA: Service Worker (D73) ----------
|
||||
Ein reiner Offline-Mantel (public/sw.js): Navigationen network-first, der
|
||||
Cache hält nur die zuletzt gesehene Fassung — die Update-Prüfung (D45)
|
||||
bleibt dadurch unverändert wahr. NICHT im Dev-Server registrieren: dort
|
||||
würde der Worker die HMR-Seite cachen; der Zweig fällt im Dev als toter
|
||||
Code weg. Relativer Pfad, damit der Scope dem Auslieferungsort folgt
|
||||
(Pages unter /werkbaum/, prod an der Wurzel). Auf file:// und http gibt es
|
||||
keinen (nutzbaren) serviceWorker — das Scheitern ist geschluckt, die App
|
||||
läuft ohne. */
|
||||
if(!import.meta.env.DEV && 'serviceWorker' in navigator){
|
||||
navigator.serviceWorker.register('sw.js').catch(() => {});
|
||||
}
|
||||
|
||||
/* ---------- Build-Hinweis (Vorschau/Dev + „latest build") ----------
|
||||
Kennzeichnet einen nicht-produktiven Build mit einem kleinen Symbol samt
|
||||
Tooltip hinter dem Titel. Gesteuert per Vite-Env `VITE_BUILD_BADGE`; ohne
|
||||
|
||||
@@ -138,6 +138,11 @@ cp frontend/dist/llms.md "$STAGE/llms.md"
|
||||
# llms.md, SPEC und Repo zeigt. Nur er liegt an der Adresse, die Agenten von
|
||||
# selbst probieren.
|
||||
cp frontend/dist/llms.txt "$STAGE/llms.txt"
|
||||
# PWA-Hülle (D73): Manifest + Icons + Service Worker — public/-Assets neben
|
||||
# der einen Datei
|
||||
cp frontend/dist/manifest.webmanifest "$STAGE/manifest.webmanifest"
|
||||
cp frontend/dist/icon-192.png frontend/dist/icon-512.png frontend/dist/icon-maskable-512.png "$STAGE/"
|
||||
cp frontend/dist/sw.js "$STAGE/sw.js"
|
||||
# Ohne diese Zuordnung liefert Apache `.md` ohne Content-Type aus und der
|
||||
# Browser rät windows-1252 (D43-Nachtrag 2). Pages braucht sie nicht.
|
||||
cp scripts/prod.htaccess "$STAGE/.htaccess"
|
||||
|
||||
@@ -20,4 +20,7 @@
|
||||
AddType text/markdown;charset=utf-8 .md
|
||||
AddType text/plain;charset=utf-8 .txt
|
||||
AddType text/plain;charset=utf-8 .werkbaum
|
||||
# PWA-Manifest (D73): ohne Zuordnung liefert Apache die unbekannte Endung
|
||||
# ohne Content-Type aus, und Chromium verwirft das Manifest dann still.
|
||||
AddType application/manifest+json .webmanifest
|
||||
</IfModule>
|
||||
|
||||
Reference in New Issue
Block a user