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:
mhoennig
2026-08-25 15:03:22 +02:00
co-authored by Claude Fable 5
parent f6785e3138
commit 19053eba1d
14 changed files with 251 additions and 17 deletions
+5
View File
@@ -88,6 +88,11 @@ jobs:
cp frontend/dist/llms.md site/llms.md cp frontend/dist/llms.md site/llms.md
# Wegweiser der llms.txt-Konvention (D43-Nachtrag 2) # Wegweiser der llms.txt-Konvention (D43-Nachtrag 2)
cp frontend/dist/llms.txt site/llms.txt 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 - name: Pages-Konfiguration
uses: actions/configure-pages@v6 uses: actions/configure-pages@v6
+3
View File
@@ -19,6 +19,9 @@ reverse.
## 2026-08-25 ## 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 - 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 - 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 - Open a local `.werkbaum` file and save the document back as a file, from the document menu
+87
View File
@@ -5389,3 +5389,90 @@ IndexedDB-Rundreise über einen Neustart: Stub-Handles überleben den
Structured Clone nicht (`DataCloneError`, planmäßig geschluckt) — echte Structured Clone nicht (`DataCloneError`, planmäßig geschluckt) — echte
Handles sind gerade dafür klonbar; dieser eine Pfad ist Code-Review statt Handles sind gerade dafür klonbar; dieser eine Pfad ist Code-Review statt
Messung. 480 Tests. 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).
+18 -17
View File
@@ -99,11 +99,11 @@
- [^] #bld.tests: Unit tests (S) - [^] #bld.tests: Unit tests (S)
- [^] #bld.pages: Published on every push (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.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) - [x] #bld.pwa: Installable as a progressive web app (M)
- [ ] #bld.pwa.manifest: A manifest with name, icons and standalone display (XS) - [x] #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 - [x] #bld.pwa.sw: A service worker that serves the app offline (S) %% network-first, cache as fallback
- [ ] #bld.pwa.update: The reload notice moves into the worker (S) %% see D45 - [x] #bld.pwa.update: The reload notice stays truthful under the worker (S) %% see D45/D73
- [ ] #bld.pwa.files: Double-click a .werkbaum file, save without any dialog (S) :#ed.files.inplace %% file_handlers + launchQueue - [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.ghpages: Deploy through a gh-pages branch (S) %% GitHub Actions instead
- [-] #bld.gfonts: Fonts from Google (XS) %% self-hosted, no third-party request - [-] #bld.gfonts: Fonts from Google (XS) %% self-hosted, no third-party request
- [ ] #be: Backend (XL) - [ ] #be: Backend (XL)
@@ -667,31 +667,32 @@
#bld.pwa #bld.pwa
Installable from the browser and startable like an app, in its own window 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 and with its own icon. Everything it needs was already in one file what
is missing is mostly the declaration that says so. was missing was mostly the declaration that says so.
#bld.pwa.manifest #bld.pwa.manifest
Name, icons, theme colour and a standalone display mode — the declaration a Name, icons, theme colour and a standalone display mode — the declaration a
browser reads before it offers to install anything. The brand mark exists browser reads before it offers to install anything. The raster icons are
and needs the raster sizes an installer asks for. rendered from the brand mark, with a padded maskable variant.
#bld.pwa.sw #bld.pwa.sw
A worker answering from the cache lets the editor start without a network. A worker lets the editor start without a network. It is deliberately dumb:
There is exactly one object to cache, because the build inlines fonts, navigations go network-first, and the cache only keeps the last version it
styles and modules; documents live in the browser anyway. Texts pulled from has seen of the one self-contained file for the offline case. Texts pulled from a URL
a URL or a pad stay online by their nature. or a pad pass through untouched and stay online by their nature.
#bld.pwa.update #bld.pwa.update
With a worker in front of the page, the reload notice has to come from it: The feared migration turned out unnecessary: because the worker only
it decides what gets served, so "the running build against the one the touches navigations and answers them network-first, the update check still
server sends" is no longer the same comparison. fetches the real server and a reload really loads the new build. The work
here was proving that, not moving code.
#bld.pwa.files #bld.pwa.files
The installed app registers for the .werkbaum extension: double-clicking a 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 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 queue. Together with the persistent write permission an installed app can
hold, editing becomes open, type, save — no dialog at all. The saving 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. launch-queue receiver. Chromium only.
#bld.ghpages #bld.ghpages
+13
View File
@@ -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 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 `commit/<sha>` in `index.html` einspritzt (danach zurücknehmen!) und
`window.fetch` überschreibt. `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 - **Neuigkeiten (D58):** `NEWS` kommt aus dem virtuellen Modul
`virtual:werkbaum-news` (Vite-Plugin in `vite.config.js`), gefüllt zur `virtual:werkbaum-news` (Vite-Plugin in `vite.config.js`), gefüllt zur
**Bauzeit** aus `docs/CHANGELOG.md` (die Notizen) und der git-Historie des **Bauzeit** aus `docs/CHANGELOG.md` (die Notizen) und der git-Historie des
+7
View File
@@ -6,6 +6,13 @@
<title>Werkbaum PSP &amp; Lean Pathfinding Editor (Work Breakdown Structure)</title> <title>Werkbaum PSP &amp; 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 &amp; Requirements."> <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 &amp; Requirements.">
<link rel="icon" type="image/svg+xml" href="../docs/brand/favicon.svg"> <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> </head>
<body> <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

+19
View File
@@ -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" }
}
+60
View File
@@ -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();
}
})());
});
+31
View File
@@ -4568,9 +4568,40 @@ try{ startLang = localStorage.getItem('werkbaum-lang') || detectLang(); }catch(_
applyLang(I18N[startLang] ? startLang : 'de'); /* setzt Texte + rendert */ applyLang(I18N[startLang] ? startLang : 'de'); /* setzt Texte + rendert */
initDocs(); /* Dokumente laden + aktiven Text in den Editor (nach Sprache) */ initDocs(); /* Dokumente laden + aktiven Text in den Editor (nach Sprache) */
if(hasFsAccess) idbLoadHandles(); /* gemerkte Datei-Handles zurückholen (D72, Stufe 2) */ 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 */ applyMobile(); /* Mobil-Verhalten (nach Sprache/Restore) anwenden */
loadRemoteSource(); /* ?sourceUrl= / ?etherpad= nachladen (asynchron, D23/D31) */ 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") ---------- /* ---------- Build-Hinweis (Vorschau/Dev + latest build") ----------
Kennzeichnet einen nicht-produktiven Build mit einem kleinen Symbol samt Kennzeichnet einen nicht-produktiven Build mit einem kleinen Symbol samt
Tooltip hinter dem Titel. Gesteuert per Vite-Env `VITE_BUILD_BADGE`; ohne Tooltip hinter dem Titel. Gesteuert per Vite-Env `VITE_BUILD_BADGE`; ohne
+5
View File
@@ -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 # llms.md, SPEC und Repo zeigt. Nur er liegt an der Adresse, die Agenten von
# selbst probieren. # selbst probieren.
cp frontend/dist/llms.txt "$STAGE/llms.txt" 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 # Ohne diese Zuordnung liefert Apache `.md` ohne Content-Type aus und der
# Browser rät windows-1252 (D43-Nachtrag 2). Pages braucht sie nicht. # Browser rät windows-1252 (D43-Nachtrag 2). Pages braucht sie nicht.
cp scripts/prod.htaccess "$STAGE/.htaccess" cp scripts/prod.htaccess "$STAGE/.htaccess"
+3
View File
@@ -20,4 +20,7 @@
AddType text/markdown;charset=utf-8 .md AddType text/markdown;charset=utf-8 .md
AddType text/plain;charset=utf-8 .txt AddType text/plain;charset=utf-8 .txt
AddType text/plain;charset=utf-8 .werkbaum 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> </IfModule>