frontend: „Werkbank" — Werkbaums eigener Plan als mitgeliefertes Dokument

Der interessanteste Beispielbaum war nur über einen ?sourceUrl=-Link
erreichbar. Er steht jetzt direkt im Dokument-Wähler.

- Eine Quelle, keine Kopie: `?raw`-Import aus
  docs/examples/example-werkbaum.werkbaum — derselben Datei, die auch der
  ?sourceUrl=-Link lädt. Vite bettet sie in die eine Ausgabedatei ein (D19),
  nichts wird nachgeladen (D20). Die Beispieldatei ist damit Build-Eingabe.
- `seedShippedDocs()` legt das Dokument genau EINMAL an — auch für
  Bestandsnutzer mit vorhandener Dokumentenliste — und merkt sich das in
  `werkbaum-seeded`. Ohne Merker gäbe es nur schlechte Alternativen: entweder
  bekämen bestehende Nutzer es nie, oder ein bewusst gelöschtes Dokument
  kehrte bei jedem Laden zurück. Das aktive Dokument bleibt unverändert.
- Fester, nicht lokalisierter Name wie „Example" (D22). Der Reset setzt jetzt
  beide mitgelieferten Dokumente zurück; eigene bleiben unangetastet.

Verifiziert im Browser: frischer Zustand -> Example + Werkbank, aktiv bleibt
Example; Wechsel auf Werkbank rendert 75 Knoten ohne Warnung; Bestandsnutzer
mit eigener Liste bekommt Werkbank ergänzt, ohne aus seinem Dokument gerissen
zu werden; nach Löschen kehrt es nicht zurück. Build bleibt eine einzelne
Datei (361 kB), Vitest 37/37.

Offen (in D27 notiert): der Text wird nach dem Anlegen nicht mehr
aktualisiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-07-27 14:06:58 +02:00
co-authored by Claude Opus 4.8
parent 6888762ac8
commit 6fb45ff37a
3 changed files with 77 additions and 6 deletions
+34
View File
@@ -619,3 +619,37 @@ Grenzen: mindestens 90 px, höchstens 85 % — die Obergrenze steht **zusätzlic
als `max-width`/`max-height` im CSS**, weil die gespeicherte Größe ein fester
px-Wert ist: schrumpft das Panel später (Zug am großen Splitter, Drehung,
Bildschirmtastatur), würde der Editor sonst auf 0 gedrückt.
## D27 — „Werkbank": Werkbaums eigener Plan als mitgeliefertes Dokument
Neben dem Beispiel (D22) liegt ein zweites mitgeliefertes Dokument im Wähler:
**„Werkbank"** — Werkbaum selbst, mit Werkbaum geplant (Bestand + mögliche
Weiterentwicklung). Damit ist der interessanteste Beispielbaum ohne Umweg über
einen `?sourceUrl=`-Link erreichbar; zugleich dient er als lebende
Projektübersicht.
**Eine Quelle, keine Kopie.** Der Text wird per `?raw`-Import aus
`docs/examples/example-werkbaum.werkbaum` gezogen — derselben Datei, die auch
der `?sourceUrl=`-Link lädt. Vite bettet sie beim Build in die eine Ausgabedatei
ein (D19), es wird nichts nachgeladen (D20). Eine zweite, abgetippte Fassung im
Quelltext würde unweigerlich auseinanderlaufen. (Nebenwirkung: die Beispieldatei
ist damit **Build-Eingabe** — Umbenennen bricht den Build. `vite.config.js`
erlaubt den Zugriff außerhalb des Roots bereits über `server.fs.allow: ['..']`,
eingeführt für das Favicon.)
**Genau einmal angelegt.** `seedShippedDocs()` fügt das Dokument auch
Bestandsnutzern hinzu, die schon eine Dokumentenliste haben, und merkt sich das
in `werkbaum-seeded`. Ohne diesen Merker gäbe es nur schlechte Alternativen:
entweder bekämen bestehende Nutzer es nie, oder ein bewusst gelöschtes Dokument
kehrte bei **jedem** Laden zurück. Das aktive Dokument bleibt beim Anlegen
unverändert — niemand wird aus seinem Text gerissen.
**Fester Name, nicht lokalisiert** — wie „Example" (D22): Dokumentnamen sind
Nutzerdaten. Der Reset (D22) setzt jetzt **beide** mitgelieferten Dokumente auf
ihren Auslieferungsstand zurück; eigene Dokumente bleiben weiterhin unangetastet.
**Offen:** Der Text wird nach dem Anlegen nicht mehr aktualisiert. Erscheint
später eine neuere Fassung des Plans, sieht sie nur, wer das Dokument löscht und
zurücksetzt oder den `?sourceUrl=`-Link öffnet. Ein Ausbau könnte in
`werkbaum-seeded` statt '1' eine Versionsnummer ablegen und ein **unverändertes**
Dokument nachziehen (bearbeitete nie) — dieselbe Adoptions-Regel wie beim
Beispiel in D22.
+7
View File
@@ -106,6 +106,13 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
`werkbaum-ui`/`werkbaum-lang`/Update-Flags — **andere Dokumente bleiben stehen**
(nicht mehr pauschal alle `werkbaum-*` löschen!). Das letzte gelöschte Dokument
wird als Beispiel neu gesät.
- Mitgeliefertes Dokument „Werkbank" (D27): `app.js` importiert
`../../docs/examples/example-werkbaum.werkbaum?raw` — die Beispieldatei ist
damit **Build-Eingabe**, Umbenennen/Verschieben bricht den Build (Zugriff
außerhalb des Roots erlaubt `server.fs.allow:['..']`). `seedShippedDocs()` legt
es **einmalig** an (auch für Bestandsnutzer) und merkt sich das in
`werkbaum-seeded`; ohne den Merker käme ein gelöschtes Dokument bei jedem Laden
zurück. `resetToDefaults()` setzt beide mitgelieferten Dokumente zurück.
- Umbenennen ist **inline** (kein `window.prompt` — in manchen Browser-Kontexten
unterdrückt): `renameDoc()` setzt `renamingId`, `renderDocMenu()` rendert dann
ein `<input class="docrename">` (Enter = `commitRename`, Esc = `cancelRename`,
+36 -6
View File
@@ -3,6 +3,11 @@ import { parse } from './parser.js';
import { computeCheapSet } from './model.js';
import { esc, renderTreeHtml } from './render.js';
import { formatWarning } from './warnings.js';
/* Werkbaum, mit Werkbaum geplant — als mitgeliefertes Dokument „Werkbank" (D27).
Dieselbe Datei, die auch per ?sourceUrl= geladen werden kann; `?raw` bettet
sie beim Build in die eine Ausgabedatei ein (D19), es wird nichts nachgeladen
(D20). Quelle bleibt docs/examples/ — keine Kopie, die auseinanderläuft. */
import WERKBAUM_DOC from '../../docs/examples/example-werkbaum.werkbaum?raw';
const INITIAL = `%% Project structure Sprint 14
[~] Website relaunch (XL) https://wiki.example.com/relaunch
@@ -1296,6 +1301,7 @@ fsBtn.addEventListener('click', () => {
erhalten. Sprache liegt weiterhin in 'werkbaum-lang'. */
const LS_UI = 'werkbaum-ui', LS_SRC = 'werkbaum-src';
const LS_DOCS = 'werkbaum-docs', LS_ACTIVE = 'werkbaum-active';
const LS_SEEDED = 'werkbaum-seeded'; /* mitgelieferte Dokumente schon angelegt? */
let restoring = false; /* unterdrückt Speichern während des Wiederherstellens */
let hadStoredUI = false; /* gab es beim Laden schon gespeicherte GUI-Einstellungen? */
@@ -1310,6 +1316,9 @@ let hadStoredUI = false; /* gab es beim Laden schon gespeicherte GUI-Einstellun
unabhängig von der UI-Sprache englisch — wie der Beispieltext selbst (breiteres
Publikum). */
const EXAMPLE_ID = 'example', EXAMPLE_NAME = 'Example';
/* Zweites mitgeliefertes Dokument: Werkbaum selbst (D27). Fester Name wie beim
Beispiel — Dokumentnamen sind Nutzerdaten und werden nicht übersetzt (D22). */
const WERKBAUM_ID = 'werkbaum', WERKBAUM_NAME = 'Werkbank';
let docs = []; /* [{id, name, text}] */
let activeId = null;
function uid(){ return 'd' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7); }
@@ -1329,6 +1338,20 @@ function persistDocs(){
if(d) localStorage.setItem(LS_SRC, d.text); /* Spiegel für Fallback/Migration */
}catch(_){}
}
/* „Werkbank" (D27) genau EINMAL anlegen — auch für Bestandsnutzer, die schon
eine Dokumentenliste haben. Der Merker `LS_SEEDED` sorgt dafür, dass ein
bewusst gelöschtes Dokument nicht bei jedem Laden zurückkehrt; ein späterer
Ausbau kann dort eine Versionsnummer statt '1' ablegen. */
function seedShippedDocs(){
let seeded = null;
try{ seeded = localStorage.getItem(LS_SEEDED); }catch(_){}
if(seeded) return;
if(!docs.some(d => d.id === WERKBAUM_ID)){
docs.push({ id: WERKBAUM_ID, name: WERKBAUM_NAME, text: WERKBAUM_DOC });
}
try{ localStorage.setItem(LS_SEEDED, '1'); }catch(_){}
}
/* Aus dem localStorage laden; bei fehlender Dokumentenliste den bestehenden
Einzeltext (oder INITIAL) als erstes Dokument migrieren. */
function loadDocs(){
@@ -1352,6 +1375,7 @@ function loadDocs(){
docs[0].id = EXAMPLE_ID;
docs[0].name = EXAMPLE_NAME;
}
seedShippedDocs();
activeId = docs.some(d => d.id === a) ? a : docs[0].id;
}
function saveSrc(){
@@ -2023,14 +2047,20 @@ function resetToDefaults(){
['werkbaum-ui','werkbaum-lang','werkbaum-html-hash','werkbaum-update-available','werkbaum-update-log']
.forEach(k => { try{ localStorage.removeItem(k); }catch(_){} });
/* Nur das Beispiel-Dokument auf den Ausgangstext zurücksetzen; existiert es
nicht (mehr), wird es neu angelegt. Andere Dokumente bleiben unberührt. */
const ex = docs.find(d => d.id === EXAMPLE_ID);
if(ex){ ex.text = INITIAL; ex.name = EXAMPLE_NAME; }
else { docs.unshift({ id: EXAMPLE_ID, name: EXAMPLE_NAME, text: INITIAL }); }
/* Nur die MITGELIEFERTEN Dokumente auf ihren Ausgangstext zurücksetzen;
existiert eines nicht (mehr), wird es neu angelegt. Eigene Dokumente des
Nutzers bleiben unberührt (D22/D27). */
const reseed = (id, name, text, vorn) => {
const d = docs.find(x => x.id === id);
if(d){ d.text = text; d.name = name; }
else if(vorn) docs.unshift({ id, name, text });
else docs.push({ id, name, text });
};
reseed(EXAMPLE_ID, EXAMPLE_NAME, INITIAL, true);
reseed(WERKBAUM_ID, WERKBAUM_NAME, WERKBAUM_DOC, false);
activeId = EXAMPLE_ID;
persistDocs();
logUpdate('🔄 Beispiel-Dokument und Einstellungen zurückgesetzt');
logUpdate('🔄 Mitgelieferte Dokumente und Einstellungen zurückgesetzt');
/* Kurze Verzögerung, damit Logging sichtbar wird, dann reload */
setTimeout(() => {