diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index e48993a..fd47693 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -2223,6 +2223,28 @@ Einzige Ausnahme ist `%%`: Der Kommentar fällt im ganzen Dokument als Erstes weg (einheitliche Regel, §1) — so lassen sich auch Beschreibungen kommentieren. +**Nachtrag — Beschreibung und Kurz-Fakten werden im Tooltip getrennt.** Die +erste Fassung hängte alles an dieselbe ` · `-Aufzählung: Auf die letzte Zeile +des Fließtexts folgte unmittelbar `· #cms · geplant · Alt+Klick: …`. Gemeldet +als „einfach hinten drangeklatscht“, und das trifft es — der Übergang war +nicht zu sehen, obwohl dort die Art der Aussage wechselt: vorn ein Satz, den +jemand geschrieben hat, hinten Metadaten, die das Werkzeug kennt. Jetzt trennt +eine **Leerzeile plus Trennstrich**. + +Der Strich ist aus `─` (U+2500) gebaut, nicht aus Bindestrichen: Das +Box-Drawing-Zeichen stößt gapless aneinander und liest sich als Linie, `---` +liest sich als Text. Ein `title` kann nichts anderes — es gibt darin kein +Markup, und ein eigenes Pop-up wollten wir gerade nicht (siehe oben). Zwei +Randbedingungen sind bewusst gesetzt: Der Strich steht **nur**, wenn beide +Teile vorhanden sind (ohne Beschreibung bleibt der Tooltip wie er war), und er +ist mit 24 Zeichen schmaler als die Fakten-Zeile, die den Sprung-Hinweis +enthält — er verbreitert den Tooltip also nie. + +**Nicht in den `aria-label`.** Dort bleibt es bei der Kommaliste mit +whitespace-normalisierter Beschreibung: Ein Screenreader läse vierundzwanzig +Striche einzeln vor, und die Trennung, die ein Auge braucht, braucht ein Ohr +nicht — die Aufzählung ist dort ohnehin schon gegliedert. + ## D41 — Querverbindungen: Krümmung statt Farbe, Pfeil auf das Gebrauchte Die Abhängigkeits-Kanten (SPEC §9) sind die erste Linienart, die nicht der Zerlegung folgt — §11 verlangte dafür eine eigene Zeichenebene. Gebaut wie der diff --git a/docs/SPEC.md b/docs/SPEC.md index 146e718..28cc41b 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -302,7 +302,11 @@ im Grafikexport. Siehe D35. **Knotenbeschreibungen (§1)** erscheinen im **Tooltip** des Knotens (zuerst der Text, dann die Kurz-Fakten wie ID und Abhängigkeiten) und im -`aria-label`. Ein Knoten mit Beschreibung trägt eine kleine **”-Marke** hinter +`aria-label`. Beschreibung und Kurz-Fakten sind im Tooltip **deutlich +getrennt**: Leerzeile und Trennstrich dazwischen, nicht bloß ein weiteres +Glied derselben Aufzählung — es sind zwei Arten von Aussage. Der Strich steht +nur, wenn es wirklich etwas zu trennen gibt, und nicht im `aria-label` (ein +Screenreader läse die Striche einzeln vor). Ein Knoten mit Beschreibung trägt eine kleine **”-Marke** hinter dem Label — sie macht die sonst unsichtbare Beschreibung auffindbar und spiegelt das `"`-Zeichen der Notation. Die Marke erscheint **nicht** im Grafikexport: Der Text selbst kann dort nicht angezeigt werden, eine Marke diff --git a/frontend/src/render.js b/frontend/src/render.js index c699d3e..da07c4c 100644 --- a/frontend/src/render.js +++ b/frontend/src/render.js @@ -42,6 +42,10 @@ export function esc(s){ } /* Escaping für Attributwerte (zusätzlich " -> "). */ function attr(s){ return esc(String(s)).replace(/"/g,'"'); } +/* Trennstrich im Knoten-Tooltip zwischen Beschreibung und Kurz-Fakten (D40). + Box-Drawing-Zeichen statt Bindestrichen: `─` stößt gapless aneinander und + liest sich als Linie, `---` als Text. */ +const TIP_RULE = '─'.repeat(24); /* Barrierefreier Name eines Knotens: Label + Status + Aufwand + Zuständige + Link. Die visuellen Badges (Größe, Tags, ↗) sind aria-hidden — ihre @@ -95,15 +99,24 @@ function nodeHtml(n, extra, opts, fold){ Querverbindungs-Ebene und den Export — beide arbeiten auf dem DOM. */ const idAttr = n.id ? ` data-id="${attr(n.id)}"` : ''; const depsAttr = n.deps && n.deps.length ? ` data-deps="${attr(n.deps.join(' '))}"` : ''; - /* Beschreibung zuerst im Tooltip (mehrzeilig, D40), dann die Kurz-Fakten. */ - const tip = [n.desc || '', - n.id ? '#' + n.id : '', - n.deps && n.deps.length ? '→ ' + n.deps.map(d => '#' + d).join(', ') : '', - effKey - ? t('heldTooltip', {eff: t('st_' + effKey), own: t('st_' + n.status.key)}) - : (n.status ? t('st_' + n.status.key) : ''), - n.optional ? t('a11yOptional') : '', t('jumpHint')] + /* Tooltip: erst die Beschreibung (mehrzeilig, D40), dann die Kurz-Fakten. + Die Fakten hängen NICHT mit ` · ` an den Fließtext an — sie sind eine + andere Art von Aussage, und in der einen Zeile ging der Übergang unter + („hinten drangeklatscht"). Deshalb Leerzeile plus Trennstrich dazwischen. + Ein `title` kann nur Text, keine Linie — der Strich ist deshalb aus + `─` gebaut. Er steht nur, wenn es wirklich etwas zu trennen gibt, und + bleibt schmaler als die Fakten-Zeile (die den Sprung-Hinweis enthält), + verbreitert den Tooltip also nicht. Der `aria-label` bekommt ihn NICHT: + ein Screenreader läse die Striche einzeln vor (nodeAria oben). */ + const facts = [n.id ? '#' + n.id : '', + n.deps && n.deps.length ? '→ ' + n.deps.map(d => '#' + d).join(', ') : '', + effKey + ? t('heldTooltip', {eff: t('st_' + effKey), own: t('st_' + n.status.key)}) + : (n.status ? t('st_' + n.status.key) : ''), + n.optional ? t('a11yOptional') : '', t('jumpHint')] .filter(Boolean).join(' · '); + const tip = n.desc && facts ? n.desc + '\n\n' + TIP_RULE + '\n' + facts + : (n.desc || facts); const title = ` title="${attr(tip)}"`; const tagsHtml = n.tags && n.tags.length ? `` diff --git a/frontend/tests/desc.test.js b/frontend/tests/desc.test.js index 00943cf..2c88f8a 100644 --- a/frontend/tests/desc.test.js +++ b/frontend/tests/desc.test.js @@ -102,11 +102,28 @@ describe('Langform — ID-Blöcke hinter `---`', () => { describe('Darstellung — Tooltip, ”-Marke, aria', () => { it('stellt die Beschreibung an den Anfang des Tooltips und setzt die Marke', () => { const {html} = render(`[ ] Auth #auth\n " Kapselt Login.`); - expect(html).toContain('title="Kapselt Login. · #auth · st_geplant · jumpHint"'); + expect(html).toContain('title="Kapselt Login.\n\n' + '─'.repeat(24) + + '\n#auth · st_geplant · jumpHint"'); expect(html).toContain(''); expect(html).toContain('aria-label="Auth, a11yStatus, a11yId, Kapselt Login."'); }); + /* Die Kurz-Fakten hingen früher mit ` · ` am Fließtext — in einer Zeile ging + der Übergang unter. Getrennt wird nur, wenn es etwas zu trennen gibt. */ + it('trennt Beschreibung und Kurz-Fakten durch Leerzeile und Strich', () => { + const {html} = render(`[ ] Auth #auth\n " Erste Zeile.\n " Zweite Zeile.`); + const tip = html.match(/title="([^"]*)"/)[1]; + const [text, rest] = tip.split('\n\n'); + expect(text).toBe('Erste Zeile.\nZweite Zeile.'); /* Fließtext bleibt zusammen */ + expect(rest.split('\n')[0]).toMatch(/^─+$/); /* Trennstrich als eigene Zeile */ + expect(rest.split('\n')[1]).toContain('#auth'); /* danach die Fakten */ + }); + + it('setzt keinen Trennstrich, wenn es keine Beschreibung gibt', () => { + const {html} = render(`[ ] Ohne #auth`); + expect(html.match(/title="([^"]*)"/)[1]).not.toContain('─'); + }); + it('ändert Knoten ohne Beschreibung nicht', () => { const {html} = render(`[ ] Ohne`); expect(html).not.toContain('desc-mark');