frontend/ci: Build-Hinweis in die App-Quelle, env-gesteuert; Prod-Build ohne

Der Latest-Build-Hinweis wandert vom Workflow-sed in die App (app.js,
mountBuildBadge), damit ihn auch der Dev-Server zeigt — ein Post-Build-sed
erreicht den Dev-Server nicht. Logik umgekehrt: Hinweis ist der Normalfall,
nur die produktive Installation schaltet ihn ab.

Drei Zustaende ueber Vite-Env VITE_BUILD_BADGE (Auswertung in app.js):
- Dev-Server (import.meta.env.DEV) -> 🔧 "Vorschau/lokaler Entwicklungsstand"
- Default `npm run build` (Env ungesetzt) -> 🚧 "latest build"; der Pages-
  Deploy nutzt den Default und traegt den Hinweis dadurch automatisch (die
  sed-Injektion entfaellt).
- `npm run build:prod` (Vite-Modus prod, .env.prod: VITE_BUILD_BADGE=none)
  -> KEIN Badge; esbuild eliminiert den Zweig als toten Code.

Verifiziert zur Laufzeit (Dev + `vite preview` auf beiden Builds): 🔧 im Dev,
🚧 im Default-Build, gar nichts im Prod-Build (Titel sauber "Werkbaum").
34 Vitest-Tests gruen, Workflow-YAML/Shell gueltig.

Doku: D16 fortgeschrieben (quellbasiert/env statt sed, Begruendung); README
(de/en) mit Prod-Build-Anleitung inkl. LICENSE-/Versions-Caveats; frontend/
CLAUDE.md; .claude/launch.json bekommt einen frontend-dist-Preview (vite
preview, Port 8138) zum Verifizieren gebauter Dateien.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-07-22 05:56:59 +02:00
co-authored by Claude Opus 4.8
parent b7bf9182ea
commit 0f27bab905
10 changed files with 135 additions and 23 deletions
+3 -8
View File
@@ -39,6 +39,9 @@ jobs:
# als ES-Module unter frontend/src/, Vite bündelt sie zu EINER
# self-contained frontend/dist/index.html (JS+CSS+Favicon inline). Tests
# laufen zuerst — schlägt Vitest fehl, wird nicht deployt.
# Hinweis: der Default-`npm run build` trägt den 🚧-„latest build"-Hinweis
# hinter dem Titel automatisch in sich (app.js/mountBuildBadge, D16) — der
# Pages-Deploy ist bewusst der latest build. Nur `build:prod` ließe ihn weg.
- name: Node einrichten
uses: actions/setup-node@v4
with:
@@ -75,16 +78,8 @@ jobs:
BUILD_VERSION="${MAJORMINOR}.${MICRO}"
COMMIT_URL="https://github.com/mhoennig/werkbaum/commit/$(git rev-parse HEAD)"
echo "Footer-Version: ${BUILD_VERSION} -> ${COMMIT_URL}"
# Latest-Build-Hinweis (Symbol + Tooltip) hinter dem Titel: kennzeichnet
# NUR diese Pages-Veröffentlichung als Entwicklungsstand. Wird bewusst
# erst hier auf der Site-Kopie injiziert (wie Version/LICENSE), damit die
# Quelle und jede andere Instanz (schlichter `npm run build`) ihn nicht
# tragen. Literale UTF-8-Zeichen (kein `#`/`&`, sonst kollidiert der
# sed-Delimiter); Anker `</h1>` ist im Bundle eindeutig.
BADGE='<span class="build-badge" role="img" aria-label="Latest build may be buggy" title="Aktueller Entwicklungsstand (latest build) kann noch Fehler enthalten · Latest development build may still be buggy" style="margin-left:.45em;font-size:.58em;vertical-align:middle;cursor:help;user-select:none;text-decoration:none">🚧</span>'
mkdir -p site
sed -e 's#\.\./LICENSE#LICENSE#g' \
-e "s#</h1>#${BADGE}</h1>#" \
-e "s#\(<a class=\"ver\" href=\"\)[^\"]*#\1${COMMIT_URL}#" \
-e "s#\(<a class=\"ver\"[^>]*>\)[0-9.]\+</a>#\1${BUILD_VERSION}</a>#" \
frontend/dist/index.html > site/index.html
+30
View File
@@ -56,6 +56,36 @@ npm run build # -> frontend/dist/index.html (eine self-contained Datei)
Die gebaute `dist/index.html` inlint JS, CSS und Favicon — **diese** Datei öffnet
also standalone per `file://` und ist zugleich das, was deployt wird.
### Build-Hinweis & eigene Produktions-Installation
Nicht-produktive Builds tragen hinter dem Titel einen kleinen Hinweis (Symbol +
Tooltip), damit klar ist, dass es **nicht** die stabile Instanz ist:
- **Dev-Server** (`npm run dev`) → 🔧 „Vorschau lokaler Entwicklungsstand"
- **Default-Build** (`npm run build`, u. a. der GitHub-Pages-Deploy) → 🚧
„Aktueller Entwicklungsstand (latest build) kann noch Fehler enthalten"
Für die **eigene produktive Installation** wird der Hinweis abgeschaltet:
```bash
cd frontend
npm ci # oder: npm install
npm run build:prod # -> frontend/dist/index.html OHNE Hinweis
```
`build:prod` läuft im Vite-Modus `prod`; `frontend/.env.prod` setzt dabei
`VITE_BUILD_BADGE=none`, wodurch der Badge-Code komplett wegoptimiert wird (er
steht dann nicht einmal mehr im Quelltext der Ausgabe). Die entstandene
`dist/index.html` legst du standalone auf deinen Webspace/Server (`file://`-
tauglich). Steuerung im Detail: `app.js` (`mountBuildBadge`), `docs/DECISIONS.md`
D16.
Zwei Dinge, die sonst nur der Pages-Workflow erledigt und die du im Eigenbetrieb
selbst geradeziehst: der Footer-Link **MIT-License** zeigt relativ auf
`../LICENSE` (lege die Datei eine Ebene über `index.html` ab oder passe den Link
an), und die **Versionsnummer** bleibt der Quelltext-Platzhalter `1.0` (der
Workflow ersetzt ihn sonst aus `VERSION` + Commit-Zahl).
## Projektdokumente
- `frontend/` — Editor · `backend/` — Kotlin/Spring (Gerüst folgt, siehe backend/README.md)
+29
View File
@@ -55,6 +55,35 @@ npm run build # -> frontend/dist/index.html (single self-contained file)
The built `dist/index.html` inlines all JS, CSS and the favicon, so **that** file
does open standalone via `file://` and is what gets deployed.
### Build hint & your own production install
Non-production builds carry a small hint next to the title (symbol + tooltip) so
it's clear this is **not** the stable instance:
- **Dev server** (`npm run dev`) → 🔧 "Preview local development build"
- **Default build** (`npm run build`, incl. the GitHub Pages deploy) → 🚧
"Latest build may still be buggy"
For **your own production install** the hint is switched off:
```bash
cd frontend
npm ci # or: npm install
npm run build:prod # -> frontend/dist/index.html WITHOUT the hint
```
`build:prod` runs Vite in mode `prod`; `frontend/.env.prod` sets
`VITE_BUILD_BADGE=none`, so the badge code is tree-shaken away entirely (it isn't
even present in the output source). Drop the resulting `dist/index.html` onto your
web space/server (standalone, `file://`-capable). Details: `app.js`
(`mountBuildBadge`), `docs/DECISIONS.md` D16.
Two things otherwise handled only by the Pages workflow, to fix up yourself when
self-hosting: the footer **MIT-License** link is relative to `../LICENSE` (place
that file one level above `index.html`, or adjust the link), and the **version
number** stays the source placeholder `1.0` (the workflow otherwise replaces it
from `VERSION` + commit count).
## Project documents
- `frontend/` — editor · `backend/` — Kotlin/Spring (scaffold to follow, see backend/README.md)
+23 -12
View File
@@ -135,18 +135,29 @@ zählt der flache CI-Klon nur einen Commit. `1.0` bleibt die Version beim lokale
Links**: „Werkbaum" → Repo-Startseite, die Versionsnummer (`<a class="ver">`) →
exakt der deployte Commit (`…/commit/<sha>`, im Build via `git rev-parse HEAD`).
**Latest-Build-Hinweis:** Der Pages-Deploy ist der jeweils frischeste
Entwicklungsstand und kann Fehler enthalten; die *eigentliche* (stabile) Instanz
läuft woanders. Damit nur diese Veröffentlichung als „latest build" markiert ist,
injiziert der Workflow — nach demselben Muster wie Version/LICENSE, also **nur auf
der Site-Kopie** — hinter dem Titel (`</h1>`, im Bundle eindeutiger Anker) ein
kleines Symbol mit Tooltip (`<span class="build-badge">🚧</span>`, zweisprachiger
`title`). Die Quelle bleibt unberührt: ein schlichter `npm run build` (z. B. für
die stabile Instanz) erzeugt die Datei **ohne** Hinweis. Bewusst nicht als
i18n-UI-Text im `I18N`-Objekt, weil es kein Produkt-Feature ist, sondern
Deploy-Metainformation genau dieser Pipeline (D14: die Quelle nicht um
Deploy-Spezifika erweitern). Umgesetzt mit literalen UTF-8-Zeichen im
`sed`-Replacement (kein `#`/`&`, sonst kollidiert der Delimiter).
**Build-Hinweis (Vorschau / „latest build"):** Nicht-produktive Builds tragen
hinter dem Titel ein kleines Symbol samt Tooltip, damit klar ist, dass es nicht
die *eigentliche* (stabile) Instanz ist. Drei Zustände, gesteuert per Vite-Env
`VITE_BUILD_BADGE` (Auswertung in `app.js`, `mountBuildBadge`):
- **Dev-Server** (`import.meta.env.DEV`) → 🔧 „Vorschau lokaler Entwicklungsstand".
- **Default-Build** `npm run build` (Env ungesetzt) → 🚧 „latest build …". Der
GitHub-Pages-Deploy nutzt genau diesen Default und trägt den Hinweis dadurch
automatisch — **keine** `sed`-Injektion mehr nötig.
- **Produktions-Build** `npm run build:prod` (Vite-Modus `prod`, `frontend/.env.prod`
setzt `VITE_BUILD_BADGE=none`) → **kein** Badge; esbuild eliminiert den Zweig
als toten Code, das Symbol steht dann nicht einmal mehr im Ausgabe-Quelltext.
Damit trägt einzig die echte produktive Installation keinen Hinweis. **Warum in
die App-Quelle statt per Workflow-`sed` (frühere Lösung):** Nur so sieht der
Dev-Server den Hinweis ebenfalls — ein Post-Build-`sed` erreicht den Dev-Server
nicht, der die Quelle direkt ausliefert. Die Umkehrung „Hinweis ist der
Normalfall, Prod schaltet ab" passt zudem zur Anforderung (nur Prod bleibt sauber)
und macht den Default sicher: wer den Prod-Schritt vergisst, veröffentlicht einen
sichtbar als Entwicklungsstand markierten Build, nicht versehentlich einen als
stabil wirkenden. Bewusst **kein** i18n-UI-Text im `I18N`-Objekt (kein
Produkt-Feature, sondern Build-Metainformation; D14) — der `title` ist knapp
zweisprachig (DE · EN).
(Nummerierung: D15 war bereits für den kompakten Modus vergeben, daher D16.)
+1 -1
View File
@@ -52,7 +52,7 @@ Abhaken beim Erledigen; neue Aufgaben unten anfügen.
- [x] GitHub-Pages-Workflow angelegt (`.github/workflows/pages.yml`,
siehe docs/DECISIONS.md D16).
## Phase 3 — Integrationen (siehe docs/ROADMAP.md)
## Phase 3 — Integrationen (siehe [ROADMAP](ROADMAP.md))
- [ ] Backend-Gerüst per Spring Initializr in `backend/` anlegen
(Kotlin, Gradle Kotlin DSL, JDK 21; Konventionen: backend/CLAUDE.md).
- [ ] SVG-Renderer (Layout-Engine) als gemeinsame Basis für Export und
+6
View File
@@ -0,0 +1,6 @@
# Produktions-Build (`npm run build:prod`, Vite-Modus „prod").
# Schaltet den Build-Hinweis hinter dem Titel ab — nur die echte produktive
# Installation läuft ohne das 🔧/🚧-Symbol. Alle anderen Builds (Dev-Server und
# der Default-`npm run build`, u. a. der GitHub-Pages-Deploy) zeigen ihn.
# Siehe app.js (mountBuildBadge) und docs/DECISIONS.md D16.
VITE_BUILD_BADGE=none
+7 -2
View File
@@ -15,8 +15,13 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
in **eine** self-contained `dist/index.html` — die bleibt `file://`-tauglich
(D16) und ist die Deploy-Artefakt-Quelle (Pages-Workflow, siehe README).
- `npm --prefix frontend test` — Vitest (`tests/**/*.test.js`).
- `node_modules/` und `dist/` sind ge-`.gitignore`-t; `package-lock.json` ist
eingecheckt (der Workflow nutzt `npm ci`).
- `npm --prefix frontend run build:prod` — Produktions-Build **ohne** den
Build-Hinweis hinter dem Titel (Vite-Modus `prod`, `.env.prod` setzt
`VITE_BUILD_BADGE=none`). Nur die echte produktive Installation nutzt diesen
Weg; Dev-Server (🔧) und Default-`build` (🚧, u. a. Pages-Deploy) zeigen den
Hinweis. Logik: `mountBuildBadge()` in `app.js` (D16).
- `node_modules/` und `dist/` sind ge-`.gitignore`-t; `.env.prod` und
`package-lock.json` sind eingecheckt (der Workflow nutzt `npm ci`).
## Konventionen
- Vanilla HTML/CSS/JS, ES-Module; keine Frameworks. Testwerkzeug: Vitest.
+1
View File
@@ -7,6 +7,7 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"build:prod": "vite build --mode prod",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest"
+33
View File
@@ -1103,3 +1103,36 @@ let startLang = 'de';
try{ startLang = localStorage.getItem('werkbaum-lang') || 'de'; }catch(_){}
applyLang(I18N[startLang] ? startLang : 'de'); /* setzt Texte + rendert */
applyMobile(); /* Mobil-Verhalten (nach Sprache/Restore) anwenden */
/* ---------- 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
Vorgabe entscheidet der Modus:
Dev-Server (`npm run dev`) -> import.meta.env.DEV -> 'dev' (🔧 Vorschau)
Default-Build (`npm run build`) -> Voreinstellung -> 'latest' (🚧)
`npm run build:prod` -> .env.prod: none -> KEIN Badge
So trägt einzig die echte Produktions-Installation keinen Hinweis. Bewusst
kein I18N-Text (Deploy-Metainfo, nicht Produkt-Feature; D14/D16) — der
Tooltip ist knapp zweisprachig (DE · EN). */
function mountBuildBadge(){
const kind = import.meta.env.VITE_BUILD_BADGE
|| (import.meta.env.DEV ? 'dev' : 'latest');
if(kind === 'none') return;
const BADGES = {
dev: {icon:'🔧', label:'Preview build (local dev)',
title:'Vorschau lokaler Entwicklungsstand · Preview (local dev build)'},
latest: {icon:'🚧', label:'Latest build may be buggy',
title:'Aktueller Entwicklungsstand (latest build) kann noch Fehler enthalten · Latest development build may still be buggy'},
};
const b = BADGES[kind];
const h1 = document.querySelector('header h1');
if(!b || !h1) return;
const el = document.createElement('span');
el.className = 'build-badge';
el.setAttribute('role', 'img');
el.setAttribute('aria-label', b.label);
el.title = b.title;
el.textContent = b.icon;
h1.appendChild(el);
}
mountBuildBadge();
+2
View File
@@ -50,6 +50,8 @@
body.fullscreen .app,
body.fullscreen .site-footer{max-width:none}
h1{font-size:1.25rem;font-weight:600;letter-spacing:-.01em}
/* Build-Hinweis hinter dem Titel (nur nicht-produktive Builds, siehe app.js) */
.build-badge{margin-left:.45em;font-size:.58em;line-height:1;vertical-align:middle;cursor:help;user-select:none;text-decoration:none}
header p{margin-top:4px;color:var(--muted);font-size:.9rem}
.sub-short{display:none} /* Kurzfassung nur auf kleinem Bildschirm */
.langsel{