feat: Falt-Voreinstellungen im Diagramm-Kopf (D44)

Ein Menü mit zwei Einträgen: alle aufklappen, unter Größe M zuklappen.

Das Kriterium ist die Größe, nicht die Tiefe. Die Ebene ist eine Eigenschaft
der Schreibweise — ein fein zerlegter Zweig verschwände bei derselben Zahl
früher als ein grob notierter. Die T-Shirt-Größe sagt etwas über die Arbeit,
und ab M muss ohnehin zerlegt sein (SPEC §5): „unter M" ist genau die Menge,
die keine Zerlegung mehr braucht.

Ohne Größenangabe wird nicht zugeklappt. Der günstigste Pfad wertet fehlende
Größen als M (D18), aber das ist eine Kostenannahme, keine Aussage des Autors.

Beide Einträge beschreiben einen vollständigen Zustand — sonst hinge die
Wirkung vom Vorzustand ab. Rückgängig nimmt den ganzen Vorgang in einem
Schritt zurück.

Umgesetzt über denselben Weg wie das einzelne Umklappen; der Voll-Rewrite ist
dafür aus writeFoldToText() in writeAllFoldMarks() gewandert. Die
Rückfall-Kette gilt unverändert: Im Werkbaum-Plan ist der Zustand wegen einer
Fokusmarke nicht in Marken ausdrückbar, dort trägt die Sitzungs-Überlagerung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 10:31:04 +02:00
co-authored by Claude Fable 5
parent 7e2c7cd3e0
commit 9bbe7d78cc
8 changed files with 232 additions and 17 deletions
+31 -1
View File
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { parse, setFoldMark } from '../src/parser.js';
import { initialCollapsed, computeCheapSet } from '../src/model.js';
import { initialCollapsed, computeCheapSet, belowM } from '../src/model.js';
import { renderTreeHtml } from '../src/render.js';
const t = key => key;
@@ -252,3 +252,33 @@ describe('Günstigster Pfad an eingeklappten Knoten', () => {
expect(stations(html)).toEqual(['N']);
});
});
/* 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)]);
it('trifft XS und S, nicht M und darüber', () => {
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],
['e', false], ['f', false]]);
});
it('trifft Knoten OHNE Größenangabe nicht', () => {
/* Der günstigste Pfad rechnet fehlende Größen als M (D18) — das ist eine
Kostenannahme, keine Aussage des Autors, und faltet hier nichts zu. */
expect(groessen('[ ] ohne')).toEqual([['ohne', false]]);
});
it('ist von Status und Optionalität unabhängig', () => {
expect(groessen('[x] a (S)\n[-] b (S)\n[ ] c (L)').map(([, b]) => b))
.toEqual([true, true, false]);
});
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);
});
});