fix: Beschreibung und Kurz-Fakten im Tooltip trennen

Bisher hing alles an derselben ` · `-Aufzählung: Auf die letzte Zeile des
Fließtexts folgte unmittelbar `· #cms · geplant · Alt+Klick: …`. 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: Leerzeile plus Trennstrich dazwischen. 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; mehr kann ein
`title` nicht, dort gibt es kein Markup.

Zwei Randbedingungen: Der Strich steht nur, wenn beide Teile da sind (ohne
Beschreibung bleibt der Tooltip unverändert), und mit 24 Zeichen ist er
schmaler als die Fakten-Zeile mit dem Sprung-Hinweis — er verbreitert den
Tooltip also nie.

Nicht in den aria-label: Ein Screenreader läse vierundzwanzig Striche einzeln
vor, und die Kommaliste dort ist ohnehin schon gegliedert.

2 neue Tests (199 gesamt), der bestehende Tooltip-Test nachgezogen.
SPEC §9 und D40 (Nachtrag) ebenso.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 07:54:12 +02:00
co-authored by Claude Fable 5
parent 037db30d96
commit e223734d58
4 changed files with 66 additions and 10 deletions
+22
View File
@@ -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 Erstes weg (einheitliche Regel, §1) — so lassen sich auch Beschreibungen
kommentieren. 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 ## D41 — Querverbindungen: Krümmung statt Farbe, Pfeil auf das Gebrauchte
Die Abhängigkeits-Kanten (SPEC §9) sind die erste Linienart, die nicht der 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 Zerlegung folgt — §11 verlangte dafür eine eigene Zeichenebene. Gebaut wie der
+5 -1
View File
@@ -302,7 +302,11 @@ im Grafikexport. Siehe D35.
**Knotenbeschreibungen (§1)** erscheinen im **Tooltip** des Knotens (zuerst **Knotenbeschreibungen (§1)** erscheinen im **Tooltip** des Knotens (zuerst
der Text, dann die Kurz-Fakten wie ID und Abhängigkeiten) und im 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 dem Label — sie macht die sonst unsichtbare Beschreibung auffindbar und
spiegelt das `"`-Zeichen der Notation. Die Marke erscheint **nicht** im spiegelt das `"`-Zeichen der Notation. Die Marke erscheint **nicht** im
Grafikexport: Der Text selbst kann dort nicht angezeigt werden, eine Marke Grafikexport: Der Text selbst kann dort nicht angezeigt werden, eine Marke
+21 -8
View File
@@ -42,6 +42,10 @@ export function esc(s){
} }
/* Escaping für Attributwerte (zusätzlich " -> &quot;). */ /* Escaping für Attributwerte (zusätzlich " -> &quot;). */
function attr(s){ return esc(String(s)).replace(/"/g,'&quot;'); } function attr(s){ return esc(String(s)).replace(/"/g,'&quot;'); }
/* 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 + /* Barrierefreier Name eines Knotens: Label + Status + Aufwand + Zuständige +
Link. Die visuellen Badges (Größe, Tags, ) sind aria-hidden ihre 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. */ Querverbindungs-Ebene und den Export beide arbeiten auf dem DOM. */
const idAttr = n.id ? ` data-id="${attr(n.id)}"` : ''; const idAttr = n.id ? ` data-id="${attr(n.id)}"` : '';
const depsAttr = n.deps && n.deps.length ? ` data-deps="${attr(n.deps.join(' '))}"` : ''; const depsAttr = n.deps && n.deps.length ? ` data-deps="${attr(n.deps.join(' '))}"` : '';
/* Beschreibung zuerst im Tooltip (mehrzeilig, D40), dann die Kurz-Fakten. */ /* Tooltip: erst die Beschreibung (mehrzeilig, D40), dann die Kurz-Fakten.
const tip = [n.desc || '', Die Fakten hängen NICHT mit ` · ` an den Fließtext an sie sind eine
n.id ? '#' + n.id : '', andere Art von Aussage, und in der einen Zeile ging der Übergang unter
n.deps && n.deps.length ? '→ ' + n.deps.map(d => '#' + d).join(', ') : '', (hinten drangeklatscht"). Deshalb Leerzeile plus Trennstrich dazwischen.
effKey Ein `title` kann nur Text, keine Linie der Strich ist deshalb aus
? t('heldTooltip', {eff: t('st_' + effKey), own: t('st_' + n.status.key)}) `` gebaut. Er steht nur, wenn es wirklich etwas zu trennen gibt, und
: (n.status ? t('st_' + n.status.key) : ''), bleibt schmaler als die Fakten-Zeile (die den Sprung-Hinweis enthält),
n.optional ? t('a11yOptional') : '', t('jumpHint')] 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(' · '); .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 title = ` title="${attr(tip)}"`;
const tagsHtml = n.tags && n.tags.length const tagsHtml = n.tags && n.tags.length
? `<span class="tags" aria-hidden="true">${n.tags.map(tag => `<span class="tag">${esc(tag)}</span>`).join('')}</span>` ? `<span class="tags" aria-hidden="true">${n.tags.map(tag => `<span class="tag">${esc(tag)}</span>`).join('')}</span>`
+18 -1
View File
@@ -102,11 +102,28 @@ describe('Langform — ID-Blöcke hinter `---`', () => {
describe('Darstellung — Tooltip, ”-Marke, aria', () => { describe('Darstellung — Tooltip, ”-Marke, aria', () => {
it('stellt die Beschreibung an den Anfang des Tooltips und setzt die Marke', () => { it('stellt die Beschreibung an den Anfang des Tooltips und setzt die Marke', () => {
const {html} = render(`[ ] Auth #auth\n " Kapselt Login.`); 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('<span class="desc-mark" aria-hidden="true">”</span>'); expect(html).toContain('<span class="desc-mark" aria-hidden="true">”</span>');
expect(html).toContain('aria-label="Auth, a11yStatus, a11yId, Kapselt Login."'); 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', () => { it('ändert Knoten ohne Beschreibung nicht', () => {
const {html} = render(`[ ] Ohne`); const {html} = render(`[ ] Ohne`);
expect(html).not.toContain('desc-mark'); expect(html).not.toContain('desc-mark');