diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 54d4882..53211d4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,6 +19,7 @@ reverse. ## 2026-08-27 +- A ticket ref like `#US-123` in a node title no longer breaks across two lines at its hyphen - Create a Taiga story from a diagram node: the node window offers "create story" and "create story + tasks" — log in once (the token stays in the browser), pick the project, and the ref lands in the line as `#US-123`, undoable - Sub-packages become Taiga tasks via a checkbox dialog: required and optional children start checked, in `|`/`=` groups only realized alternatives, done children unchecked, discarded ones absent — each created task writes its `#T-…` ref to its line - The project dialog is prefilled from the inherited `&taiga.slug` keyword, and a creation without or against the inherited assignment writes the keyword back to the node's line diff --git a/docs/SPEC.md b/docs/SPEC.md index 9dd9622..576e59d 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -565,7 +565,9 @@ Siehe D29. trifft der 23-px-Abzweig die Mitte der **ersten Zeile** (die Zeilenhöhe bleibt fest); der Optional-Kreis (§9) sitzt auf dem Abzweig, nicht auf der Knotenmitte. Der Grafikexport misst die gerenderten Zeilen und gibt sie als - einzelne Textzeilen aus. + einzelne Textzeilen aus. Eine **Ticket-Referenz** im Label (`#US-123` / + `#T-1234`, §11) bricht dabei **nie mitten im Token** um — auch nicht am + Bindestrich, an dem der Browser eine zu breit geratene Zeile sonst bricht. - Dasselbe gilt **waagerecht**: Im horizontalen Fächer trifft der Stiel die **Knotenmitte**, nicht die Mitte der Zelle. Beides fällt nur zusammen, solange der Knoten in seiner Zelle zentriert steht — ein Knoten mit any-of-Kindern diff --git a/frontend/src/render.js b/frontend/src/render.js index 2ce7e02..78c595e 100644 --- a/frontend/src/render.js +++ b/frontend/src/render.js @@ -93,6 +93,18 @@ export function wrapLabel(label, max = 32){ return lines; } +/* Ticket-Referenzen (`#US-123`/`#T-1234`, SPEC §11) im Label bleiben ein + Stück: `wrapLabel()` bricht nur an Leerzeichen, aber der Browser bricht + eine Zeile, die breiter als `max-width` gerät (32ch misst die Ziffer „0", + Buchstaben sind breiter), zusätzlich an jedem BINDESTRICH — und eine Ref, + die als `#US-`/`123` über zwei Zeilen geht, ist nicht mehr als Ref zu + lesen. Läuft auf dem schon escapten Label-Text; die Spanne enthält nur + Text, `labelLines()` (Grafikexport) misst ihre Textknoten unverändert mit. */ +function markTicketRefs(html){ + return html.replace(/(^|\s)(#(?:US|T)-\d+)(?=\s|$)/g, + (all, pre, ref) => `${pre}${ref}`); +} + /* Barrierefreier Name eines Knotens: Label + Status + Aufwand + Zuständige + Link. Die visuellen Badges (Größe, Tags, ↗) sind aria-hidden — ihre Information steckt hier, sonst würde der Screenreader Kryptisches („M", @@ -272,7 +284,7 @@ function nodeHtml(n, extra, opts, fold){ Der `textContent` behielte sonst keine Wortgrenze, und alles, was den Knotentext liest (Export, Fokusmarken-Schlüssel), bekäme zusammengeklebte Wörter. */ - esc(wrapLabel(n.label).join('\n')) + + markTicketRefs(esc(wrapLabel(n.label).join('\n'))) + /* ”-Marke (D40): macht die sonst unsichtbare Beschreibung auffindbar (Lehre aus D25) — spiegelt das "-Zeichen der Notation. Nicht im Export: Der Text selbst kann dort nicht diff --git a/frontend/src/style.css b/frontend/src/style.css index bbdcda3..aef0b50 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -775,6 +775,10 @@ } /* Nur die dunkle (statuslose) Wurzel — Begründung beim Falt-Chip oben. */ .root-node:not(.st-idee):not(.st-geplant):not(.st-arbeit):not(.st-durchstich):not(.st-fertig):not(.st-prod):not(.st-highrisk):not(.st-verworfen) .nid{color:rgba(255,255,255,.75)} + /* Ticket-Referenz im Label (SPEC §11): als Ganzes gehalten — der Browser + bräche eine zu breit geratene Zeile sonst am Bindestrich mitten im Token + (`#US-`/`123`). Die Spanne setzt render.js (markTicketRefs). */ + .node .tref{white-space:nowrap} /* Der Knopf trägt das Zeichen selbst statt eines Icons — in der Größe der SVGs daneben, damit die Kopfzeile ihre Höhe behält. */ .copybtn.idsbtn span{ diff --git a/frontend/tests/render.test.js b/frontend/tests/render.test.js index c555999..5f0c56b 100644 --- a/frontend/tests/render.test.js +++ b/frontend/tests/render.test.js @@ -182,3 +182,26 @@ describe('showIds — die ID in einer eigenen Zeile über dem Titel', () => { expect(html).toContain('<b: X'); }); }); + +/* Ticket-Referenzen im Label (SPEC §11) sind als Ganzes gehalten: eine + `tref`-Spanne mit `white-space:nowrap`, damit der Browser eine zu breit + geratene Zeile nicht am Bindestrich mitten im Token bricht. */ +describe('renderTreeHtml — Ticket-Referenzen brechen nicht um', () => { + const one = text => renderTreeHtml(parse(text).roots, {t, cheapSet: new Set()}).html; + + it('umspannt eine freistehende Ref im Label', () => { + expect(one('- Login bauen #auth #US-123')) + .toContain('#US-123'); + expect(one('- #x: Detail #T-4567 klären')) + .toContain('#T-4567'); + }); + + it('umspannt auch den D60-Knoten, dessen Label die Ref ist', () => { + expect(one('- #US-123')).toContain('#US-123'); + }); + + it('lässt Zitier-Klammern und Nicht-Refs in Ruhe', () => { + expect(one('- Erwähnt (#US-123) nur #x')).not.toContain('tref'); + expect(one('- #auth: Backend-US-123 Modul')).not.toContain('tref'); + }); +});