frontend: „Was ist neu?" — neu in Produktion mit gelbem Strahlenkranz
Dokumente von außen (mitgeliefert D27, ?sourceUrl= D23) ändern sich, ohne dass der Betrachter es merkt. Sie zeigen jetzt, was sich seit seinem letzten Besuch getan hat. - „Neu" heißt bewusst NICHT „Zeile hinzugefügt", sondern **neu in Produktion**: ein Knoten trägt jetzt [^] und tat es in der zuletzt gesehenen Fassung nicht. Ein Zeilendiff meldete vor allem Rauschen; die Nachricht, die zählt, ist was live gegangen ist. Nebeneffekt: es leuchtet eine Handvoll Knoten, nicht dreißig. - Basis ist die zuletzt GESEHENE Fassung je Dokument (`werkbaum-seen`), nicht die letzte Auslieferung — wer Fassungen überspringt, sieht alles seither. Fortgeschrieben wird erst beim Bestätigen, sonst wäre die Meldung nach einem Neuladen weg, bevor sie jemand bemerkt. Beim Erstkontakt leuchtet nichts. - Knoten-Identität ist der Label-Pfad, nicht die Zeilennummer: Umeinrücken und Umsortieren erzeugen keine Falschmeldungen; gleichnamige Geschwister per Index; Umbenennen gilt als neuer Knoten (der Text ist der Vertrag, D14). - Darstellung: gelber Strahlenkranz nach außen, kein Blinken (WCAG 2.2.2/2.3.1; Blinken zöge dauerhaft den Blick statt einmal zu melden). Außen, weil die Füllung dem Status gehört (SPEC §4). Zusammen mit der Cursor-Zeile: Tinte innen, Gelb außen. Nicht im Druck und nicht im Grafikexport — die Markierung hängt am persönlichen Besuchsstand. - Knopf im Diagramm-Kopf nur, wenn es etwas gibt; nennt die Anzahl, Klick bestätigt. Kein Dauer-Umschalter. - Bei selbst bearbeitetem mitgeliefertem Dokument wird nichts hervorgehoben — dort fehlt die saubere Vergleichsbasis (D27). Fehler beim Bauen, der zuerst durchrutschte: Die Menge wurde beim Laden aus einem eigenen Parse-Durchlauf berechnet. Der Zähler stimmte, aber kein Knoten leuchtete — `Set.has()` prüft Objektidentität, und die gerenderten Knoten kamen aus einem anderen Parse. `render()` bildet die Menge jetzt bei jedem Durchlauf aus den gerade geparsten Wurzeln; vorgehalten wird nur der geparste Basisbaum. Verifiziert: 9 neue Modelltests (Statuswechsel, neuer [^]-Knoten, nicht-[^] ignoriert, unverändert ignoriert, Erstkontakt leer, Umsortieren/Tabs ohne Falschmeldung, gleiches Label unter verschiedenen Eltern, gleichnamige Geschwister, verworfene). Im Browser: Erstkontakt setzt nur die Basis (Knopf versteckt); zurückgedrehte Basis lässt genau die erwarteten Knoten leuchten (Zähler 3, dann 1); Bestätigen räumt auf und überlebt das Neuladen; ohne Bestätigen bleibt die Meldung über ein Neuladen stehen. Vitest 46/46. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
68abdc6625
commit
7a52277b84
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parse } from '../src/parser.js';
|
||||
import { freshProdSet, statusByKey } from '../src/model.js';
|
||||
|
||||
const roots = txt => parse(txt).roots;
|
||||
const labels = set => [...set].map(n => n.label).sort();
|
||||
|
||||
/* „Neu" heißt: jetzt [^] in Produktion, vorher nicht (D28). */
|
||||
describe('freshProdSet — neu in Produktion', () => {
|
||||
const VORHER = `[~] Werkzeug (L)
|
||||
- [x] Parser (S)
|
||||
- [ ] Export (M)
|
||||
- [ ] SVG (S)
|
||||
- [ ] PNG (S)`;
|
||||
|
||||
it('meldet einen Knoten, dessen Status auf [^] gewechselt ist', () => {
|
||||
const NACHHER = VORHER.replace('- [x] Parser (S)', '- [^] Parser (S)');
|
||||
expect(labels(freshProdSet(roots(VORHER), roots(NACHHER)))).toEqual(['Parser']);
|
||||
});
|
||||
|
||||
it('meldet einen neu hinzugekommenen [^]-Knoten', () => {
|
||||
const NACHHER = VORHER + '\n - [^] Doku (S)';
|
||||
expect(labels(freshProdSet(roots(VORHER), roots(NACHHER)))).toEqual(['Doku']);
|
||||
});
|
||||
|
||||
it('meldet neue Knoten, die NICHT in Produktion sind, nicht', () => {
|
||||
const NACHHER = VORHER + '\n - [ ] Doku (S)\n - [?] Idee (S)';
|
||||
expect(freshProdSet(roots(VORHER), roots(NACHHER)).size).toBe(0);
|
||||
});
|
||||
|
||||
it('meldet unveränderte [^]-Knoten nicht (nur den Übergang)', () => {
|
||||
const A = '[^] Fertig (S)';
|
||||
expect(freshProdSet(roots(A), roots(A)).size).toBe(0);
|
||||
});
|
||||
|
||||
it('ohne Vergleichsfassung ist nichts neu — sonst leuchtete beim Erstkontakt alles', () => {
|
||||
expect(freshProdSet(null, roots('[^] A (S)\n[^] B (S)')).size).toBe(0);
|
||||
});
|
||||
|
||||
it('Umeinrücken und Umsortieren erzeugen keine Falschmeldung (Identität = Label-Pfad)', () => {
|
||||
const A = '[~] Wurzel (L)\n - [^] Eins (S)\n - [x] Zwei (S)';
|
||||
const B = '[~] Wurzel (L)\n\t- [x] Zwei (S)\n\t- [^] Eins (S)'; /* Tab statt Leerzeichen, getauscht */
|
||||
expect(freshProdSet(roots(A), roots(B)).size).toBe(0);
|
||||
});
|
||||
|
||||
it('gleicher Label unter verschiedenen Eltern wird auseinandergehalten', () => {
|
||||
const A = '[~] W (L)\n - [ ] Alpha (M)\n - [ ] Test (S)\n - [ ] Beta (M)\n - [ ] Test (S)';
|
||||
const B = A.replace('- [ ] Beta (M)\n - [ ] Test (S)', '- [ ] Beta (M)\n - [^] Test (S)');
|
||||
const set = freshProdSet(roots(A), roots(B));
|
||||
expect(set.size).toBe(1);
|
||||
/* der gemeldete „Test" hängt unter Beta, nicht unter Alpha */
|
||||
const key = [...statusByKey(roots(B)).keys()].find(k => k.includes('Beta') && k.includes('Test'));
|
||||
expect(key).toBeTruthy();
|
||||
});
|
||||
|
||||
it('gleichnamige Geschwister bekommen getrennte Identitäten', () => {
|
||||
const A = '[~] W (L)\n - [ ] Doppelt (S)\n - [ ] Doppelt (S)';
|
||||
const B = '[~] W (L)\n - [ ] Doppelt (S)\n - [^] Doppelt (S)';
|
||||
expect(freshProdSet(roots(A), roots(B)).size).toBe(1);
|
||||
});
|
||||
|
||||
it('erfasst auch verworfene Teilbäume nicht als Produktion', () => {
|
||||
const A = '[~] W (L)\n - [-] Weg (S)';
|
||||
const B = '[~] W (L)\n - [-] Weg (S)\n - [^] Da (S)';
|
||||
expect(labels(freshProdSet(roots(A), roots(B)))).toEqual(['Da']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user