change: Falt-Voreinstellung wird ein Umschalter, Schwelle ab M abwärts (D44)

Statt eines Aufklapp-Menüs mit zwei Einträgen jetzt ein Umschalter neben
„verworfene einblenden" und „günstigster Pfad". Die beiden Einträge waren nie
unabhängige Befehle, sondern die zwei Stellungen einer Sache — jeder
beschreibt einen vollständigen Faltzustand, zusammen decken sie ihn ab.

Die Schwelle schließt M jetzt ein (atMostM statt belowM): Ein M-Paket ist
selbst noch die Einheit, die man als Ganzes plant, seine Zerlegung ist schon
das Innere. Offen bleiben L, XL, XXL — und Knoten ohne Größenangabe.

Der Zustand des Knopfes wird bei jedem Rendern am Baum abgelesen, nicht
gemerkt: Klappt jemand danach einen Knoten von Hand um, springt er von selbst
heraus, statt etwas zu behaupten, das nicht mehr stimmt. Deshalb auch nicht
persistiert — der Faltzustand steht im Text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 10:44:03 +02:00
co-authored by Claude Fable 5
parent 9bbe7d78cc
commit 417006960f
8 changed files with 124 additions and 144 deletions
+48 -32
View File
@@ -2718,22 +2718,27 @@ und der Footer-Link rückt zwischen Versionsnummer und Copyright — zur
Werkzeug-Ecke des Footers statt ans Ende hinter die Rechtstexte. Der
Dateiname bleibt als Linktext (übersetzungsfrei), die Wurzel-Lage bleibt.
## D44 — Falt-Voreinstellungen: ein Menü, „unter M“ statt einer Tiefenzahl
## D44 — Falt-Umschalter: „ab M abwärts“ statt einer Tiefenzahl
Einzeln zu falten (D38) reicht für einen Plan mit 167 Knoten nicht — man will
den Baum am Stück auf eine Arbeitshöhe bringen. Der Diagramm-Kopf bekommt
deshalb ein Menü mit zwei Einträgen: **alle aufklappen** und **unter Größe M
zuklappen**.
dafür einen **Umschalter** neben „verworfene einblenden“ und „günstigster
Pfad“: gedrückt ist alles **ab Größe M abwärts** zugeklappt, nicht gedrückt
ist der ganze Baum offen.
**Das Kriterium ist die Größe, nicht die Tiefe.** Die naheliegende Alternative
wäre „bis Ebene n aufklappen“ gewesen. Die Ebene ist aber eine Eigenschaft der
Schreibweise, nicht der Sache: Wie tief etwas steht, hängt davon ab, wie fein
der Autor zerlegt hat, und ein sorgfältig aufgeschlüsselter Zweig verschwände
bei derselben Zahl früher als ein grob notierter. Die T-Shirt-Größe sagt
dagegen etwas über die **Arbeit**, und die Notation hat dafür bereits eine
Schwelle: Ab `M` **muss** ein Element zerlegt sein (§5/D8). „Unter M“ ist also
genau die Menge, die keine Zerlegung mehr braucht — was darunter hängt, ist
Detail. Die Voreinstellung zeigt damit den Plan auf der Höhe, auf der noch
Entscheidungen anstehen.
dagegen etwas über die **Arbeit** und weil `M` die kleinste Größe ist, die
überhaupt eine Zerlegung verlangt (§5/D8), ist „M und kleiner“ genau die
Menge, deren Inneres Detail ist. Übrig bleibt der Plan auf der Höhe von `L`
aufwärts, also dort, wo die großen Brocken stehen.
*(Zuerst gebaut war die Schwelle **unter** M — offen blieb dann auch jedes
`M`. Der Nutzer hat sie auf `M` einschließlich korrigiert: Ein M-Paket ist
selbst noch die Einheit, die man als Ganzes plant; seine Zerlegung ist schon
das Innere.)*
**Ohne Größenangabe wird nicht zugeklappt.** Der günstigste Pfad wertet eine
fehlende Größe als `M` (D18) — das wäre hier der falsche Präzedenzfall: Dort
@@ -2743,15 +2748,34 @@ nichts gesagt; einen Zweig deswegen zu verbergen, behandelte eine Vermutung
wie eine Angabe. Ein Knoten ohne Größe bleibt also offen — und fällt dadurch
angenehmerweise auf.
**Beide Einträge beschreiben einen vollständigen Zustand.** „Unter M
zuklappen“ klappt nicht nur zu, es klappt alles **andere auf**. Sonst wäre die
Wirkung vom Vorzustand abhängig und zweimal Drücken ergäbe zweierlei — für
eine Voreinstellung die falsche Eigenschaft. Der Preis: Ein von Hand gesetztes
`>` an einem großen Knoten wird dabei aufgelöst. Das ist vertretbar, weil der
Nutzer gerade ausdrücklich eine Gesamtansicht angefordert hat, und weil
**Rückgängig** den ganzen Vorgang in einem Schritt zurücknimmt (nachgemessen:
`replaceTextUndoable` schreibt genau einmal, Strg+Z stellt Text und Diagramm
wieder her).
**Ein Umschalter, kein Menü.** Zuerst gebaut war ein Aufklapp-Menü mit zwei
Einträgen („alle aufklappen“ / „zuklappen“); der Nutzer wollte einen
Umschalter. Das ist die bessere Form, und der Grund ist nicht nur Platz: Die
beiden Einträge waren nie unabhängige Befehle, sondern die **zwei Stellungen
einer Sache** — jeder beschreibt einen vollständigen Faltzustand, und
zusammen decken sie ihn ganz ab. Genau das ist ein Umschalter. Ein Menü
verlangte zwei Klicks für etwas, das einen braucht, und der Diagramm-Kopf hat
das Idiom längst zweimal (D18, D4).
**Der Zustand wird abgelesen, nicht gemerkt.** Ein gemerktes Flag würde lügen,
sobald jemand danach einen einzelnen Knoten umklappt. Stattdessen prüft
`render()` bei jedem Durchlauf, ob **jeder** faltbare Knoten so steht, wie die
Voreinstellung ihn stellen würde; nur dann ist der Knopf gedrückt. Er springt
damit von selbst heraus, wenn man von Hand etwas aufklappt, und von selbst
wieder hinein, wenn man es zurücknimmt (beides nachgemessen). Deshalb wird er
auch **nicht** in `werkbaum-ui` persistiert: Der Faltzustand steht im Text
(D38-Nachtrag 2), der Knopf liest ihn nur ab. Sonderfall: Gäbe es gar keinen
Knoten bis `M`, wären beide Stellungen derselbe Zustand — dann bleibt der
Knopf heraußen, statt gedrückt dazusitzen, ohne dass etwas zugeklappt ist.
**Beide Stellungen beschreiben einen vollständigen Zustand.** Zuklappen klappt
also alles **andere auf**. Sonst wäre die Wirkung vom Vorzustand abhängig und
zweimal Drücken ergäbe zweierlei — für einen Umschalter die falsche
Eigenschaft. Der Preis: Ein von Hand gesetztes `>` an einem großen Knoten wird
dabei aufgelöst. Vertretbar, weil der Nutzer gerade ausdrücklich eine
Gesamtansicht angefordert hat, und weil **Rückgängig** den ganzen Vorgang in
einem Schritt zurücknimmt (nachgemessen: `replaceTextUndoable` schreibt genau
einmal, Strg+Z stellt Text und Diagramm wieder her).
**Umgesetzt über denselben Weg wie das einzelne Umklappen** — die
Sitzungs-Überlagerungen setzen, dann in den Text schreiben (D38-Nachtrag 2).
@@ -2762,18 +2786,10 @@ Rückfall-Kette unverändert — ist der Zustand in Marken **nicht ausdrückbar*
wird nicht geschrieben und die Überlagerung trägt ihn für die Sitzung.
Genau der Fall tritt im mitgelieferten Werkbaum-Plan auf und ist beim Bauen
nachgemessen worden: Zwei Knoten sind kleiner als `M` und haben Kinder
(Zeile 94 und 100), aber in Zeile 103 steht eine Fokusmarke `!!!` unterhalb
von 94 — die holt ihren Knoten immer wieder hervor, „94 eingeklappt“ ist als
Markensatz also nicht schreibbar. Das Bild stimmt trotzdem (die Überlagerung
gewinnt in `render()`), der Text bleibt unangetastet. In einem Plan ohne `!!!`
werden die Marken geschrieben: `- [ ] > Klein (S)` und `- [ ] > Teil 3 (S)`,
während `(L)`, `(XL)` und der Knoten ohne Größe offen bleiben.
**Ein Menü, nicht zwei Knöpfe.** Der Diagramm-Kopf trägt schon acht
Bedienelemente und bricht auf dem Telefon knapp nicht um (D17). Zwei weitere
Icon-Knöpfe hätten das gekostet — und die beiden Einträge brauchen **Text**:
„unter Größe M zuklappen“ ist als Piktogramm nicht zu sagen. Dasselbe Idiom
wie das Download-Menü (D17), nur auf **allen** Bildschirmgrößen aufklappend
statt nur auf schmalen. Nachgemessen bei 375 px: Das Menü steht mit 159 px
vollständig im Bild, die Kopfzeile bleibt einzeilig.
nachgemessen worden: In Zeile 103 steht eine Fokusmarke `!!!` unterhalb eines
Knotens, der zugeklappt werden soll — die holt ihren Knoten immer wieder
hervor, dieser Markensatz ist also nicht schreibbar. Das Bild stimmt trotzdem
(die Überlagerung gewinnt in `render()`), der Text bleibt unangetastet. In
einem Plan ohne `!!!` werden die Marken geschrieben: `- [ ] > Mittel (M)`,
`- [ ] > Klein (S)` und `- [ ] > Teil 3 (M)`, während `(L)`, `(XL)` und der
Knoten ohne Größe offen bleiben.
+13 -9
View File
@@ -699,15 +699,19 @@ fokussierten Knoten (WAI-ARIA-Baum-Idiom).
weil eine Fokusmarke `!!!` ihren Knoten immer wieder hervorholt): Dann wird
lieber nichts geschrieben, als einen Text zu hinterlassen, der etwas anderes
sagt als das Bild.
- **Für den ganzen Baum** gibt es im Diagramm-Kopf ein Menü mit zwei
Voreinstellungen: **alle aufklappen** und **unter Größe M zuklappen**. Beide
beschreiben einen **vollständigen** Faltzustand — was die Voreinstellung
nicht zuklappt, ist danach offen; „unter Größe M“ meint jeden Knoten mit
Kindern, dessen **angegebene** Größe (§5) kleiner als `M` ist. Ein Knoten
**ohne** Größenangabe wird nicht zugeklappt: Der günstigste Pfad rechnet
fehlende Größen zwar als `M` (§9), das ist aber eine Kostenannahme und keine
Aussage des Autors. Geschrieben wird wie beim einzelnen Umklappen — ein
einziger Rückgängig-Schritt für den ganzen Vorgang. Siehe D44.
- **Für den ganzen Baum** gibt es im Diagramm-Kopf einen **Umschalter**:
gedrückt ist alles **ab Größe M abwärts** zugeklappt, nicht gedrückt ist der
ganze Baum **offen**. Betroffen ist jeder Knoten mit Kindern, dessen
**angegebene** Größe (§5) `M` oder kleiner ist — offen bleiben `L`, `XL`,
`XXL`. Ein Knoten **ohne** Größenangabe wird nicht zugeklappt: Der günstigste
Pfad rechnet fehlende Größen zwar als `M` (unten), das ist aber eine
Kostenannahme und keine Aussage des Autors.
- Beide Stellungen beschreiben einen **vollständigen** Faltzustand — was der
Umschalter nicht zuklappt, ist danach offen. Sein Zustand wird **nicht
gemerkt, sondern am Baum abgelesen**: Wer danach einen Knoten von Hand
umklappt, sieht ihn von selbst herausspringen. Geschrieben wird wie beim
einzelnen Umklappen — ein einziger Rückgängig-Schritt für den ganzen
Vorgang. Siehe D44.
- Faltung ist **reine Ansicht**: Warnungen aus eingeklappten Teilbäumen werden
weiter gemeldet (sie gelten dem Text), und der günstigste Pfad rechnet
unverändert über den ganzen Baum.
+8 -5
View File
@@ -340,12 +340,15 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
Mobil die Tastatur hoch (`keyboardOnJump(true)`). Bei `src.readOnly` (Pad,
D31) wird nicht geschrieben, dort trägt weiter `foldOverrides` — die
Überlagerungen werden nach erfolgreichem Schreiben geleert, sonst maskieren
sie den Text. **Voreinstellungen** im Diagramm-Kopf (`applyFoldPreset`, D44)
gehen denselben Weg: Überlagerungen für ALLE faltbaren Knoten setzen
(`belowM(n)` bzw. `false`), dann `writeAllFoldMarks()` — der aus
sie den Text. Der **Umschalter** im Diagramm-Kopf (`applyFoldPreset`, D44)
geht denselben Weg: Überlagerungen für ALLE faltbaren Knoten setzen
(`atMostM(n)` bzw. `false`), dann `writeAllFoldMarks()` — der aus
`writeFoldToText()` herausgelöste Voll-Rewrite; einen minimalen Patch gibt es
hier nicht. Ein Knoten **ohne** Größe zählt nicht als „unter M" (anders als
bei den Pfadkosten, D18). **`replaceTextUndoable()` meldet bei unveränderten Text
hier nicht. Ein Knoten **ohne** Größe zählt nicht als „bis M" (anders als
bei den Pfadkosten, D18). Sein `aria-pressed` wird **in `render()` am Baum
abgelesen**, nicht gemerkt und nicht persistiert — sonst behauptete er nach
einem einzelnen Umklappen etwas Falsches.
**`replaceTextUndoable()` meldet bei unveränderten Text
`false`** (D38-Nachtrag 3): Ohne Textänderung feuert kein `input`, also läuft
kein `render()` — der Aufrufer muss dann selbst zeichnen. Sonst bleibt das
Bild stehen (aufgefallen beim Aufklappen eines nur per Überlagerung
+3 -15
View File
@@ -159,23 +159,11 @@
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/></svg>
<span class="freshcount" id="freshCount" aria-hidden="true">0</span>
</button>
<!-- Faltung für den ganzen Baum (SPEC §9, D44). Ein Auslöser, zwei
Optionen — deshalb ein Aufklapp-Menü und keine zwei Knöpfe. -->
<div class="foldgroup" id="foldGroup">
<button type="button" class="copybtn foldtrigger" id="foldTrigger" aria-haspopup="true" aria-expanded="false" data-i18n-title="foldMenu" data-i18n-aria="foldMenu" title="Teilbäume auf- und zuklappen" aria-label="Teilbäume auf- und zuklappen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 4h13M8 9h13M8 15h13M8 20h13M2 6l2.5 2.5L2 11"/></svg>
</button>
<div class="foldmenu">
<button type="button" class="copybtn foldbtn" id="foldExpandAll">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v18M8 7l4-4 4 4M8 17l4 4 4-4"/></svg>
<span class="fold-label" data-i18n="foldExpandAll">alle aufklappen</span>
</button>
<button type="button" class="copybtn foldbtn" id="foldCollapseSmall">
<!-- Faltung für den ganzen Baum (SPEC §9, D44): ein Umschalter wie die
beiden Nachbarn. Gedrückt = alles ab Größe M abwärts ist zugeklappt. -->
<button type="button" class="copybtn togglebtn" id="foldSmallBtn" aria-pressed="false" data-i18n-title="foldSmallTooltip" data-i18n-aria="foldSmallTooltip" title="Knoten der Größe M und kleiner zuklappen erneut drücken klappt alle wieder auf" aria-label="Knoten der Größe M und kleiner zuklappen erneut drücken klappt alle wieder auf">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21V3M8 8l4 4 4-4M8 16l4-4 4 4"/></svg>
<span class="fold-label" data-i18n="foldCollapseSmall">unter Größe M zuklappen</span>
</button>
</div>
</div>
<div class="seg" role="radiogroup" aria-label="Darstellung">
<label data-i18n-title="modeHorizontal" title="Horizontal Organigramm, Diagramm über dem Editor">
<input type="radio" name="layout" value="horizontal" checked data-i18n-aria="modeHorizontal" aria-label="Horizontal Organigramm">
+35 -53
View File
@@ -1,6 +1,6 @@
import './style.css';
import { parse, setFoldMark } from './parser.js';
import { computeCheapPlan, freshProdSet, initialCollapsed, nodeKeys, effectiveStatus, belowM } from './model.js';
import { computeCheapPlan, freshProdSet, initialCollapsed, nodeKeys, effectiveStatus, atMostM } from './model.js';
import { esc, renderTreeHtml } from './render.js';
import { formatWarning } from './warnings.js';
import { padUrls } from './remote.js';
@@ -124,13 +124,25 @@ function render(){
const initFold = initialCollapsed(roots, true);
const keys = nodeKeys(roots);
const collapsedSet = new Set();
let foldSmallExact = true, foldSmallAny = false;
foldByLine = new Map();
keys.forEach((key, n) => {
const ov = foldOverrides.get(key);
const collapsed = (ov !== undefined ? ov : initFold.has(n)) && n.children.length > 0;
if(collapsed) collapsedSet.add(n);
foldByLine.set(n.line, {key, collapsed, canFold: n.children.length > 0});
/* Der Umschalter (D44) merkt sich nichts, er liest ab: gedrückt genau
dann, wenn JEDER faltbare Knoten so steht, wie die Voreinstellung ihn
stellen würde. */
if(n.children.length > 0){
if(atMostM(n)) foldSmallAny = true;
if(collapsed !== atMostM(n)) foldSmallExact = false;
}
});
/* `foldSmallAny`: Gäbe es gar keinen Knoten bis M, wären zugeklappt" und
alles offen" derselbe Zustand der Knopf säße dann gedrückt da, ohne
dass etwas zugeklappt ist. */
foldSmallBtn.setAttribute('aria-pressed', foldSmallExact && foldSmallAny ? 'true' : 'false');
const r = renderTreeHtml(roots, {t, showDiscarded, cheapPath: cheapPathOn, cheapSet,
freshSet, collapsedSet,
effStatus: effectiveStatus(roots)});
@@ -1001,7 +1013,7 @@ function applyFoldPreset(mode){
if(!roots.length) return;
nodeKeys(roots).forEach((key, n) => {
if(!n.children.length) return; /* nur faltbare Knoten */
foldOverrides.set(key, mode === 'small' ? belowM(n) : false);
foldOverrides.set(key, mode === 'small' ? atMostM(n) : false);
});
if(!src.readOnly && writeAllFoldMarks(roots, desiredFoldKeys(roots))) foldOverrides.clear();
else render(); /* Pad o. nicht ausdrückbar: Überlagerung trägt */
@@ -1399,6 +1411,18 @@ function discardedShown(){ return showc.getAttribute('aria-pressed') === 'true';
function setDiscarded(on){ showc.setAttribute('aria-pressed', on ? 'true' : 'false'); }
showc.addEventListener('click', () => { setDiscarded(!discardedShown()); render(); saveUI(); });
/* Faltung für den ganzen Baum (SPEC §9, D44): ein Umschalter wie die beiden
Nachbarn. Gedrückt heißt alles unter Größe M ist zugeklappt". Der Zustand
wird nicht gemerkt, sondern bei jedem Rendern am Baum ABGELESEN
(`syncFoldSmallBtn`) klappt jemand danach einen Knoten von Hand um, geht
der Knopf von selbst heraus, statt etwas zu behaupten, das nicht mehr
stimmt. Deshalb auch nicht in `werkbaum-ui` persistiert: Der Faltzustand
steht im Text (D38-Nachtrag 2), der Knopf liest ihn nur ab. */
const foldSmallBtn = document.getElementById('foldSmallBtn');
foldSmallBtn.addEventListener('click', () => {
applyFoldPreset(foldSmallBtn.getAttribute('aria-pressed') === 'true' ? 'all' : 'small');
});
/* Günstigster-Pfad-Hervorhebung an/aus */
const cheapBtn = document.getElementById('cheapBtn');
cheapBtn.addEventListener('click', () => {
@@ -1466,30 +1490,6 @@ document.addEventListener('click', e => {
if(dlGroup.classList.contains('open') && !dlGroup.contains(e.target)) closeDlMenu();
});
/* Faltung für den ganzen Baum (SPEC §9, D44) dasselbe Menü-Idiom, hier auf
allen Bildschirmgrößen, weil die Einträge Text tragen. */
const foldGroup = document.getElementById('foldGroup');
const foldTrigger = document.getElementById('foldTrigger');
function closeFoldMenu(){
foldGroup.classList.remove('open');
foldTrigger.setAttribute('aria-expanded', 'false');
}
foldTrigger.addEventListener('click', () => {
const open = foldGroup.classList.toggle('open');
foldTrigger.setAttribute('aria-expanded', String(open));
});
document.addEventListener('click', e => {
if(foldGroup.classList.contains('open') && !foldGroup.contains(e.target)) closeFoldMenu();
});
document.addEventListener('keydown', e => {
if(e.key === 'Escape' && foldGroup.classList.contains('open')){ closeFoldMenu(); foldTrigger.focus(); }
});
document.getElementById('foldExpandAll').addEventListener('click', () => {
closeFoldMenu(); applyFoldPreset('all');
});
document.getElementById('foldCollapseSmall').addEventListener('click', () => {
closeFoldMenu(); applyFoldPreset('small');
});
/* ---------- Internationalisierung (DE/EN/ES/FR) ---------- */
const I18N = {
@@ -1516,9 +1516,7 @@ const I18N = {
riskTooltip:"High Risk Aufwand noch unklar.",
discardedTooltip:"Verworfene Knoten samt Teilbaum ein-/ausblenden",
cheapTooltip:"Günstigsten Pfad hervorheben nicht benötigte Alternativen treten zurück",
foldMenu:"Teilbäume auf- und zuklappen",
foldExpandAll:"alle aufklappen",
foldCollapseSmall:"unter Größe M zuklappen",
foldSmallTooltip:"Knoten der Größe M und kleiner zuklappen erneut drücken klappt alle wieder auf",
implicitSizeTooltip:"Keine Größe angegeben für die Kostenschätzung als M angenommen",
fullscreenTooltip:"Vollbild Panels nutzen die ganze Fensterbreite",
brandTooltip:"„Werkbaum“ bedeutet so viel wie Werk-Baum — der Baum des Projektstrukturplans (WBS).",
@@ -1599,9 +1597,7 @@ const I18N = {
riskTooltip:"High risk effort still unclear.",
discardedTooltip:"Show/hide discarded nodes and their subtree",
cheapTooltip:"Highlight the cheapest path unneeded alternatives recede",
foldMenu:"Expand and collapse subtrees",
foldExpandAll:"expand all",
foldCollapseSmall:"collapse below size M",
foldSmallTooltip:"Collapse nodes of size M and smaller press again to expand them all",
implicitSizeTooltip:"No size given assumed as M for the cost estimate",
fullscreenTooltip:"Full screen panels use the full window width",
brandTooltip:"“Werkbaum” means roughly work tree — the tree of the work breakdown structure (WBS).",
@@ -1682,9 +1678,7 @@ const I18N = {
riskTooltip:"Alto riesgo esfuerzo aún incierto.",
discardedTooltip:"Mostrar u ocultar los nodos descartados y su subárbol",
cheapTooltip:"Resaltar la ruta más económica: las alternativas no necesarias se atenúan",
foldMenu:"Desplegar y plegar subárboles",
foldExpandAll:"desplegar todo",
foldCollapseSmall:"plegar por debajo de la talla M",
foldSmallTooltip:"Plegar los nodos de talla M o menor: pulsa de nuevo para desplegarlos todos",
implicitSizeTooltip:"Sin tamaño indicado: se asume M para el cálculo de costes",
fullscreenTooltip:"Pantalla completa los paneles usan todo el ancho de la ventana",
brandTooltip:"«Werkbaum» significa algo así como ‘árbol de trabajo — el árbol de la estructura de desglose del trabajo (EDT).",
@@ -1765,9 +1759,7 @@ const I18N = {
riskTooltip:"Risque élevé effort encore incertain.",
discardedTooltip:"Afficher/masquer les nœuds abandonnés et leur sous-arbre",
cheapTooltip:"Mettre en évidence le chemin le moins coûteux les alternatives inutiles s'estompent",
foldMenu:"plier et replier les sous-arbres",
foldExpandAll:"tout déplier",
foldCollapseSmall:"replier en dessous de la taille M",
foldSmallTooltip:"Replier les nœuds de taille M et moins appuyez à nouveau pour tout déplier",
implicitSizeTooltip:"Aucune taille indiquée considérée comme M pour l'estimation des coûts",
fullscreenTooltip:"Plein écran les panneaux occupent toute la largeur de la fenêtre",
brandTooltip:"« Werkbaum » signifie à peu près « arbre de travail » — larbre de lorganigramme des tâches (WBS).",
@@ -1848,9 +1840,7 @@ const I18N = {
riskTooltip:"Wysokie ryzyko nakład jeszcze niejasny.",
discardedTooltip:"Pokaż/ukryj odrzucone węzły wraz z poddrzewem",
cheapTooltip:"Wyróżnij najtańszą ścieżkę niepotrzebne alternatywy są przygaszone",
foldMenu:"Rozwijanie i zwijanie poddrzew",
foldExpandAll:"rozwiń wszystko",
foldCollapseSmall:"zwiń poniżej rozmiaru M",
foldSmallTooltip:"Zwiń węzły o rozmiarze M i mniejsze naciśnij ponownie, aby rozwinąć wszystkie",
implicitSizeTooltip:"Nie podano rozmiaru przyjęto M do szacowania kosztów",
fullscreenTooltip:"Pełny ekran panele wykorzystują całą szerokość okna",
brandTooltip:"„Werkbaum” znaczy mniej więcej drzewo pracy — drzewo struktury podziału pracy (WBS).",
@@ -1931,9 +1921,7 @@ const I18N = {
riskTooltip:"Высокий риск – оценка ещё не ясна.",
discardedTooltip:"Показать/скрыть отклонённые узлы вместе с поддеревом",
cheapTooltip:"Выделить самый дешёвый путь — ненужные альтернативы приглушаются",
foldMenu:"Развернуть и свернуть поддеревья",
foldExpandAll:"развернуть все",
foldCollapseSmall:"свернуть меньше размера M",
foldSmallTooltip:"Свернуть узлы размера M и меньше — нажмите ещё раз, чтобы развернуть все",
implicitSizeTooltip:"Размер не указан — для оценки затрат принят как M",
fullscreenTooltip:"Полный экран – панели занимают всю ширину окна",
brandTooltip:"«Werkbaum» примерно означает ‚дерево работ’ — дерево структуры декомпозиции работ (СДР).",
@@ -2014,9 +2002,7 @@ const I18N = {
riskTooltip:"उच्च जोखिम – प्रयास अभी अस्पष्ट।",
discardedTooltip:"अस्वीकृत नोड्स और उनके उप-वृक्ष दिखाएँ/छिपाएँ",
cheapTooltip:"सबसे किफ़ायती पथ को उजागर करें – अनावश्यक विकल्प मंद हो जाते हैं",
foldMenu:"उपवृक्ष खोलें और समेटें",
foldExpandAll:"सभी खोलें",
foldCollapseSmall:"आकार M से छोटे समेटें",
foldSmallTooltip:"आकार M और उससे छोटे नोड समेटें – सभी को खोलने के लिए फिर दबाएँ",
implicitSizeTooltip:"कोई आकार नहीं दिया गया – लागत अनुमान के लिए M माना गया",
fullscreenTooltip:"पूर्ण स्क्रीन – पैनल पूरी विंडो चौड़ाई का उपयोग करते हैं",
brandTooltip:"„Werkbaum“ का अर्थ लगभग ‚कार्य-वृक्ष‘ है — कार्य विभाजन संरचना (WBS) का वृक्ष।",
@@ -2086,9 +2072,7 @@ const I18N = {
fullscreenTooltip:"全屏——面板占据整个窗口宽度",
discardedTooltip:"显示/隐藏已放弃的节点及其子树",
cheapTooltip:"突出显示成本最低的路径——不需要的备选项将淡化",
foldMenu:"展开和折叠子树",
foldExpandAll:"全部展开",
foldCollapseSmall:"折叠小于 M 的节点",
foldSmallTooltip:"折叠尺寸 M 及更小的节点——再次点击可全部展开",
implicitSizeTooltip:"未指定尺寸——成本估算时按 M 计",
ghostTooltip:"从 M 号起,元素应进一步细分。",
jumpHint:"Alt+点击:跳转到文本中的该行",
@@ -2169,9 +2153,7 @@ const I18N = {
fullscreenTooltip:"全画面 — パネルがウィンドウ幅いっぱいを使用",
discardedTooltip:"破棄したノードとその下位ツリーを表示/非表示",
cheapTooltip:"最も低コストの経路を強調 – 不要な選択肢は控えめに表示",
foldMenu:"サブツリーの展開と折りたたみ",
foldExpandAll:"すべて展開",
foldCollapseSmall:"サイズ M 未満を折りたたむ",
foldSmallTooltip:"サイズ M 以下のノードを折りたたむ – もう一度押すとすべて展開",
implicitSizeTooltip:"サイズ未指定 – コスト見積もりのため M として扱う",
ghostTooltip:"サイズ M 以上の要素はさらに分解すべきです。",
jumpHint:"Alt+クリック:テキストの該当行へ移動",
+7 -6
View File
@@ -21,12 +21,13 @@ export function needsBreakdown(n){
return !!n.size && SIZE_RANK[n.size] >= SIZE_RANK.M && !n.children.length;
}
/* Faltung-Voreinstellung alles unter Größe M zuklappen" (SPEC §9, D44):
Knoten mit ANGEGEBENER Größe kleiner als M. Fehlt die Angabe, wird nichts
angenommen der günstigste Pfad rechnet sie zwar als M (D18), das ist aber
eine Kostenannahme des Werkzeugs und keine Aussage des Autors; danach den
Baum zuzuklappen hieße, eine Vermutung wie eine Angabe zu behandeln. */
export function belowM(n){ return !!n.size && SIZE_RANK[n.size] < SIZE_RANK.M; }
/* Faltung-Voreinstellung Größe M und kleiner zuklappen" (SPEC §9, D44):
Knoten mit ANGEGEBENER Größe bis einschließlich M offen bleiben also nur
L, XL, XXL. Fehlt die Angabe, wird nichts angenommen: Der günstigste Pfad
rechnet sie zwar als M (D18), das ist aber eine Kostenannahme des Werkzeugs
und keine Aussage des Autors; danach den Baum zuzuklappen hieße, eine
Vermutung wie eine Angabe zu behandeln. */
export function atMostM(n){ return !!n.size && SIZE_RANK[n.size] <= SIZE_RANK.M; }
/* Sichtbare Kinder: verworfene ausblenden, außer showDiscarded ist gesetzt. */
export function visibleChildren(n, showDiscarded){
-14
View File
@@ -397,20 +397,6 @@
.dlgroup{display:inline-flex;align-items:center;gap:3px}
.dlmenu{display:inline-flex;gap:3px}
.dltrigger{display:none}
/* Faltung für den ganzen Baum (SPEC §9, D44): Anders als die Download-Gruppe
klappt diese IMMER auf die beiden Einträge tragen Text, der inline die
Kopfzeile sprengte. Idiom und Maße wie dort. */
.foldgroup{display:inline-flex;align-items:center;position:relative}
.foldtrigger[aria-expanded="true"]{border-color:var(--or);color:var(--or)}
.foldmenu{display:none}
.foldgroup.open .foldmenu{
display:flex;flex-direction:column;gap:5px;
position:absolute;top:calc(100% + 6px);right:0;z-index:40;
background:var(--card);border:1px solid rgba(36,52,71,.15);
border-radius:8px;padding:5px;box-shadow:0 6px 18px rgba(36,52,71,.28);
}
.foldbtn{gap:6px;width:100%;justify-content:flex-start;padding-inline:8px;white-space:nowrap}
.fold-label{font-size:.68rem;font-weight:500;letter-spacing:.01em}
.legendbtn{display:none} /* nur auf kleinem Bildschirm sichtbar */
.panetoggle{display:none} /* ebenso: dort gibt es nur einen Bereich */
/* Zurückschreiben der Faltung (D38-Nachtrag 2): Für die Dauer des SYNCHRONEN
+8 -8
View File
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { parse, setFoldMark } from '../src/parser.js';
import { initialCollapsed, computeCheapSet, belowM } from '../src/model.js';
import { initialCollapsed, computeCheapSet, atMostM } from '../src/model.js';
import { renderTreeHtml } from '../src/render.js';
const t = key => key;
@@ -256,12 +256,12 @@ describe('Günstigster Pfad an eingeklappten Knoten', () => {
/* Voreinstellung unter Größe M zuklappen" im Diagramm-Kopf (SPEC §9, D44).
Das Falten selbst liegt in app.js (kein headless-Testziel); geprüft wird
das Prädikat, das entscheidet, WELCHE Knoten es trifft. */
describe('belowM — Auswahl für „unter Größe M zuklappen"', () => {
const groessen = txt => roots(txt).map(n => [n.label, belowM(n)]);
describe('atMostM — Auswahl für „Größe M und kleiner zuklappen"', () => {
const groessen = txt => roots(txt).map(n => [n.label, atMostM(n)]);
it('trifft XS und S, nicht M und darüber', () => {
it('trifft XS, S und M — offen bleiben nur L, XL, XXL', () => {
expect(groessen('[ ] a (XS)\n[ ] b (S)\n[ ] c (M)\n[ ] d (L)\n[ ] e (XL)\n[ ] f (XXL)'))
.toEqual([['a', true], ['b', true], ['c', false], ['d', false],
.toEqual([['a', true], ['b', true], ['c', true], ['d', false],
['e', false], ['f', false]]);
});
@@ -277,8 +277,8 @@ describe('belowM — Auswahl für „unter Größe M zuklappen"', () => {
});
it('gilt auch für Knoten tief im Baum', () => {
const [w] = roots('[ ] W (XL)\n - [ ] A (S)\n - [ ] A1 (XS)\n - [ ] B (L)');
expect([belowM(w), ...w.children.map(belowM)]).toEqual([false, true, false]);
expect(belowM(w.children[0].children[0])).toBe(true);
const [w] = roots('[ ] W (XL)\n - [ ] A (M)\n - [ ] A1 (XS)\n - [ ] B (L)');
expect([atMostM(w), ...w.children.map(atMostM)]).toEqual([false, true, false]);
expect(atMostM(w.children[0].children[0])).toBe(true);
});
});