diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 81fa374..9b72d0f 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -1660,3 +1660,45 @@ kopierten Regeln — hätte jede künftige Layoutänderung doppelt pflegen lasse Im Modell ist `'xor'` ein eigener Gate-Wert (`gateOf`), damit die `mixedGate`-Warnung Mischungen mit `|` von selbst meldet; alle Disjunktiv-Abfragen prüfen `!== 'and'`. + +## D36 — Knoten-IDs (`#auth`) umgesetzt: eng gefasst, sichtbar nur im Tooltip +Der erste Baustein der Phase-4-Kette (ohne IDs keine Abhängigkeiten, ohne die +kein effektiver Status). Vier Festlegungen, die §11 offen ließ: + +**Zeichenmenge wie `@name`, nicht „whitespace-frei“.** §11 sagte „ein +whitespace-freier Bezeichner“; umgesetzt ist die engere Menge aus §7 +(Unicode-Buchstaben, Ziffern, `.`, `_`, `-`). Drei Gründe: Konsistenz mit den +beiden Nachbarn (`@name` heute, `&tag` reserviert mit derselben Menge, D34); +ein `#a/b` oder `#a:b` liefe sonst in dieselben Kollisionen, vor denen §11 bei +`:` und Pfaden gerade ausweicht; und enger → weiter ist später kompatibel +möglich, umgekehrt nicht. + +**Nur alleinstehend angesetzt** (`(^|\s)#…`, wie beim reservierten `&tag`): +„C#“ bleibt ein Label, und — entscheidend für den nächsten Schritt — die +Abhängigkeits-Schreibweise `:#a,#b` wird **nicht** als ID gefressen, weil dort +`:` bzw. `,` vor dem `#` steht. Die ID-Extraktion muss beim Bau der +Abhängigkeiten also nicht angefasst werden. + +**Das erste Token ist die ID, weitere bleiben im Label.** Die ID benennt genau +einen Knoten — mehr als eine pro Zeile ergibt keinen Sinn. Alles nach dem +ersten Treffer bleibt unangetastet stehen, denn dort wohnt die reservierte +Ticket-Referenz (`… #123 …`, §11): Sie soll sichtbar im Label bleiben, bis das +Taiga-Feature sie auflöst. Deshalb wurden auch die drei `#`-Vorkommen im +mitgelieferten Plan eingeklammert (`(#auth)`, `(#123)`) — als Erwähnungen sind +sie keine IDs, und `#123` wäre sonst doppelt vergeben gewesen (Zeile 25/162). + +**Sichtbar im Tooltip und `aria-label`, sonst nirgends.** Die ID gehört nicht +zum Label (sonst änderte das spätere Entfernen die Knoten-Identität der +„Was ist neu?“-Anzeige, D28). Ganz unsichtbar wäre aber nutzerfeindlich — +getippter Text verschwände spurlos. Der Tooltip zeigt `#id` als erste Zeile, +der Screenreader bekommt `a11yId`; ein eigenes Badge bekommt sie erst, wenn +etwas darauf zeigt (Querverbindungen, §11) — die Knoten-Ecken sind belegt +(D18). + +**Doppelte ID: Warnung an der späteren Zeile, mit Nennung der ersten.** Die +Meldung zeigt dorthin, wo man eingreifen muss, und `{firstLine}` erspart das +Suchen. Die spätere ID gilt trotzdem am Knoten (fehlertolerant wie §4); +welcher Knoten bei Verweisen „gewinnt“, entscheidet erst die +Abhängigkeits-Auflösung — dort ist die Warnung dann schon da. Eine Zeile, die +**nur** aus einer ID besteht, wird wie jede leere Zeile ignoriert und belegt +die ID nicht. diff --git a/docs/SPEC.md b/docs/SPEC.md index bf902db..4603227 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -19,8 +19,27 @@ dieser Reihenfolge (wichtig für Kollisionsfreiheit): 3. URL: erstes Token, das auf `https?://\S+` passt (dadurch stören `@` in URLs nicht). 4. Größe: erstes `(XS|S|M|L|XL|XXL)`, Groß-/Kleinschreibung egal. 5. Tags: alle `@name`-Vorkommen. -6. Fokusmarke: `!!!` als **alleinstehendes** Token (siehe unten). -7. Rest, whitespace-normalisiert = Label. Leeres Label ⇒ Zeile ignorieren. +6. Knoten-ID: das **erste** alleinstehend angesetzte `#name`-Token (siehe unten). +7. Fokusmarke: `!!!` als **alleinstehendes** Token (siehe unten). +8. Rest, whitespace-normalisiert = Label. Leeres Label ⇒ Zeile ignorieren. + +**Knoten-ID `#name`** — benennt einen Knoten im **ganzen Dokument** eindeutig; +sie ist die Adresse für Abhängigkeiten und Beschreibungsblöcke (§11). + +- Zeichenmenge wie bei `@name` (§7): Unicode-Buchstaben, Ziffern, `.`, `_`, `-`. + (Enger als das frühere „whitespace-frei“ aus §11 — Begründung: D36.) +- Erkannt nur **alleinstehend angesetzt** (`(^|\s)#…`): „C#“ bleibt damit ein + Label, und der für Abhängigkeiten reservierte Doppelpunkt `:#a,#b` (§11) + kollidiert nicht. +- Das **erste** solche Token der Zeile ist die ID; weitere `#`-Token bleiben im + Label stehen (dort liegt die reservierte Ticket-Referenz `#123`, §11). Eine + rein numerische ID ist zugleich die künftige Ticket-Referenz — oft ist die + Ticket-Nummer die natürliche Knoten-ID (D34). +- Die ID gehört **nicht** zum Label. Eine eigene Darstellung im Diagramm hat + sie (noch) nicht; sichtbar ist sie im Knoten-Tooltip und im `aria-label`. +- **Doppelte ID:** Warnung `duplicateId` mit beiden Zeilennummern; die spätere + ID gilt trotzdem am Knoten (fehlertolerant wie §4 — die Zeile geht nicht + verloren). **Fokusmarke `!!!`** — „schau hier hin": Der Knoten wird im Diagramm hervorgehoben und ins Bild geholt (§9). Gedacht für das gemeinsame Arbeiten an @@ -45,7 +64,13 @@ Referenz-Regex der Implementierung: ^([ \t]*)([-|+]|=(?=[ \t]))?\s*(?:\[([ ?~xX^/-])\]\s*)?(.*)$ ``` -Für die Fokusmarke (Schritt 6): +Für die Knoten-ID (Schritt 6, nur der erste Treffer): + +``` +(^|\s)#([\p{L}\p{N}._-]+) +``` + +Für die Fokusmarke (Schritt 7): ``` (^|\s)!!!(?=\s|$) @@ -532,10 +557,9 @@ das auch. Begründung und Zusammenhang: D34. - `#123` — Referenz auf externe Tickets (geplant für Taiga-Integration). Ticket-Referenzen werden **so** notiert, weil es die etablierte Kurzschreibweise ist; sie haben unter den `#`-Verwendungen Vorrang. -- `#auth` — **Knoten-ID**: ein whitespace-freier Bezeichner, der einen Knoten im - ganzen Dokument eindeutig benennt. Ziel für Abhängigkeiten und - Beschreibungsblöcke (siehe unten). Zwei Knoten mit derselben ID sind ein - Fehler und bekommen eine Warnung mit Zeilennummer (§4). +- `#auth` — **Knoten-ID**: **umgesetzt**, Definition jetzt in §1 (Zeichenmenge, + Alleinstehend-Regel, Warnung `duplicateId`). Ziel für Abhängigkeiten und + Beschreibungsblöcke (siehe unten). Beide Rollen vertragen sich: Oft **ist** die Ticket-Nummer die natürliche Knoten-ID. Als Ticket-Link behandelt wird heuristisch das rein **numerische** diff --git a/docs/TASKS.md b/docs/TASKS.md index 3427ec0..8f60755 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -71,7 +71,12 @@ entscheiden, **bevor** Code entsteht. numerisch, oft zugleich die natürliche Knoten-ID; notfalls Präfix wie `#t123`); Schlagworte gehen auf `&tag` — niedrig priorisiert, gebaut erst mit dem ersten Konsumenten (D34-Nachtrag). -- [ ] Knoten-IDs parsen; doppelte ID → Warnung mit Zeilennummer. +- [x] Knoten-IDs parsen; doppelte ID → Warnung mit Zeilennummer. + → Umgesetzt (D36): Zeichenmenge wie `@name`, nur alleinstehend angesetzt + und nur das erste Token je Zeile (`:#a,#b` und Ticket-Erwähnungen bleiben + unberührt); Warnung `duplicateId` nennt beide Zeilen; sichtbar im + Tooltip + `aria-label`; SPEC-§11-Teil nach §1 überführt; + `tests/ids.test.js`. - [ ] Abhängigkeiten `:#a,#b` parsen; unbekannte ID → Warnung, Zyklen erlaubt. - [ ] Effektiven Status rechnen (intrinsisch + Abhängigkeiten); Darstellung entscheiden — die Knotenfarbe zeigt heute den intrinsischen Status. diff --git a/docs/examples/werkbaum.werkbaum b/docs/examples/werkbaum.werkbaum index 9e66acd..ea643ce 100644 --- a/docs/examples/werkbaum.werkbaum +++ b/docs/examples/werkbaum.werkbaum @@ -9,7 +9,7 @@ - [^] People tags, bare URLs, %% comments (XS) - [^] And/or decomposition (S) - [^] Optional nodes — neither required nor an alternative (S) - - [?] Node IDs, #auth (S) %% often just the ticket number, see SPEC §11 + - [x] Node IDs (#auth) (S) %% often just the ticket number, see SPEC §11 + [?] Free tags, &tag (M) %% only together with a consumer, see D34 - [?] A lens: highlight every node tagged &x (S) - [?] Dependencies across the tree, :#auth,#api (M) @@ -22,7 +22,7 @@ - [?] A short text right at the node (S) %% indentation already means hierarchy - [?] A long block at the end, addressed by its id (S) - [-] A separate storage format for the structure (L) %% the text is the format - - [ ] Ticket references #123 (S) + - [ ] Ticket references (#123) (S) - [?] Benefit per node, not only cost (M) | [?] Another sigil next to the size (S) | [?] Story points behind the T-shirt sizes (M) @@ -159,7 +159,7 @@ | [?] Run the one JS parser inside the IDE (M) - [?] Tracker integration (XL) | [?] Taiga (L) https://taiga.io - - [ ] Resolve #123 over the REST API (M) + - [ ] Resolve "#123" over the REST API (M) - [ ] Read title, link and status (S) - [ ] Map the workflow onto the states (S) - [?] Write the status back (M) diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index d81a661..c078e39 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -47,7 +47,8 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der Alternative-Rahmen grau (Basis-CSS `ul.or`). Kein Petrol im Diagramm mehr; `var(--or)` nur noch für UI-Akzente/Logo (SPEC §9, D15). - Extraktionsreihenfolge im Parser nicht umstellen: Kommentar → Zeichen/ - Status → URL → Größe → Tags (sonst kollidiert `@` in URLs). + Status → URL → Größe → Tags → Knoten-ID → Fokusmarke (sonst kollidieren + `@` und `#` in URLs). - Fehlertoleranz (SPEC §4): der Parser erfasst die Statusbox als *beliebiges* Einzelzeichen `\[([^\]])\]` und validiert gegen `STATUS_BY_CODE`; unbekannte Codes → `parse().warnings` als `{type:'unknownStatus', line, code}`, Knoten @@ -266,6 +267,12 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der passiert: Zähler stimmte, nichts leuchtete). Vorgehalten wird nur `freshPrevRoots` (Basis, einmal geparst). Basis je Dokument in `werkbaum-seen`, fortgeschrieben **erst beim Bestätigen** über `#freshBtn`. +- Knoten-IDs `#name` (SPEC §1/D36): nur **alleinstehend angesetzt** und nur + der **erste** Treffer der Zeile (kein `/g`!) — weitere `#`-Token bleiben im + Label (reservierte Ticket-Referenzen), und `:#a,#b` (künftige Abhängigkeiten) + darf nicht gefressen werden. Zeichenmenge wie `@name`. Doppelte ID → + `{type:'duplicateId', line, id, firstLine}`; die spätere gilt trotzdem. + Keine eigene Darstellung — nur Tooltip (erste Position) und `a11yId`. - XOR-Gruppen `=` (SPEC §3/D35): Der Parser setzt `type:'xor'` (nur mit folgendem Leerraum — `=SUMME(…)` bleibt Label); der Renderer gibt `
(M) gilt: weiter untergliedern — fehlt die Untergliederung, erscheint ein Platzhalter im Diagramm.%% — als ganze Zeile oder am Zeilenende.
Personen mit @name — erscheinen unten rechts am Knoten.
+ Knoten-ID mit #name — erscheint im Tooltip des Knotens.
diff --git a/frontend/src/app.js b/frontend/src/app.js
index 7146e7b..377e55d 100644
--- a/frontend/src/app.js
+++ b/frontend/src/app.js
@@ -1102,12 +1102,13 @@ const I18N = {
empty:"Noch keine Struktur — einfach lostippen.", ghost:"…",
mixedWarn:"Zeile {line}: Unter „{label}“ sind - und | gemischt — dargestellt nach dem ersten Kind.",
xorConflictWarn:"Zeile {line}: „{label}“ ist eine weitere realisierte Alternative — eine =-Gruppe erlaubt genau eine.",
+ duplicateIdWarn:"Zeile {line}: Die ID #{id} ist schon vergeben (Zeile {firstLine}).",
st_idee:"Idee", st_geplant:"geplant", st_arbeit:"in Arbeit", st_durchstich:"Durchstich",
st_fertig:"fertig", st_prod:"in Produktion", st_highrisk:"High Risk", st_verworfen:"verworfen",
unknownStatusWarn:"Zeile {line}: unbekanntes Statuszeichen „{code}“ — als neutral dargestellt.",
sourceLoadWarn:"„{url}“ konnte nicht geladen werden ({error}). Die Datei muss per http(s) erreichbar sein und CORS erlauben (Access-Control-Allow-Origin).",
sourceTimeoutWarn:"„{url}“ hat innerhalb von {seconds} s nicht geantwortet — der Abruf wurde abgebrochen. Etherpad begrenzt, wie oft der Export geholt werden darf (serienmäßig 10-mal pro 90 s); warte einen Moment und lade dann erneut.",
- a11yStatus:"Status: {status}", a11ySize:"Aufwand: {size}", a11ySizeImplicit:"Aufwand: M (angenommen)", a11yTags:"Zuständig: {names}", a11yOptional:"optional", a11yFocusMark:"hierhin schauen", a11yLink:"verlinkt",
+ a11yStatus:"Status: {status}", a11ySize:"Aufwand: {size}", a11ySizeImplicit:"Aufwand: M (angenommen)", a11yTags:"Zuständig: {names}", a11yId:"ID: #{id}", a11yOptional:"optional", a11yFocusMark:"hierhin schauen", a11yLink:"verlinkt",
hint_indent:"Einrückung (2 Leerzeichen oder Tab) definiert die Hierarchie.",
hint_all:"Teilpaket, alle erforderlich", hint_any:"Alternative, eine wählen",
hint_xor:"Alternative, genau eine",
@@ -1119,6 +1120,7 @@ const I18N = {
hint_break:"Ab (M) gilt: weiter untergliedern — fehlt die Untergliederung, erscheint ein Platzhalter im Diagramm.",
hint_comment:"Kommentare mit %% — als ganze Zeile oder am Zeilenende.",
hint_people:"Personen mit @name — erscheinen unten rechts am Knoten.",
+ hint_id:"Knoten-ID mit #name — erscheint im Tooltip des Knotens.",
hint_jump:"Alt+Klick auf einen Knoten (mobil: langer Druck) springt zur zugehörigen Textzeile; Alt+Klick im Text holt den Knoten ins Bild."
},
en: {
@@ -1168,12 +1170,13 @@ const I18N = {
empty:"No structure yet — just start typing.", ghost:"…",
mixedWarn:"Line {line}: under “{label}”, - and | are mixed — rendered by the first child.",
xorConflictWarn:"Line {line}: “{label}” is another realized alternative — an = group allows exactly one.",
+ duplicateIdWarn:"Line {line}: ID #{id} is already taken (line {firstLine}).",
st_idee:"idea", st_geplant:"planned", st_arbeit:"in progress", st_durchstich:"walking skeleton",
st_fertig:"done", st_prod:"in production", st_highrisk:"high risk", st_verworfen:"discarded",
unknownStatusWarn:"Line {line}: unknown status code “{code}” — shown as neutral.",
sourceLoadWarn:"Could not load “{url}” ({error}). The file must be reachable via http(s) and allow CORS (Access-Control-Allow-Origin).",
sourceTimeoutWarn:"“{url}” did not answer within {seconds} s — the request was aborted. Etherpad limits how often the export may be fetched (10 times per 90 s by default); wait a moment, then reload.",
- a11yStatus:"Status: {status}", a11ySize:"Effort: {size}", a11ySizeImplicit:"Effort: M (assumed)", a11yTags:"Assigned: {names}", a11yOptional:"optional", a11yFocusMark:"look here", a11yLink:"has link",
+ a11yStatus:"Status: {status}", a11ySize:"Effort: {size}", a11ySizeImplicit:"Effort: M (assumed)", a11yTags:"Assigned: {names}", a11yId:"ID: #{id}", a11yOptional:"optional", a11yFocusMark:"look here", a11yLink:"has link",
hint_indent:"Indentation (2 spaces or a tab) defines the hierarchy.",
hint_all:"sub-task, all required", hint_any:"alternative, choose one",
hint_xor:"alternative, exactly one",
@@ -1185,6 +1188,7 @@ const I18N = {
hint_break:"From (M) on: break it down further — if the breakdown is missing, a placeholder appears in the diagram.",
hint_comment:"Comments with %% — whole line or at the end of a line.",
hint_people:"People with @name — shown at the bottom-right of the node.",
+ hint_id:"Node ID with #name — shown in the node's tooltip.",
hint_jump:"Alt+click a node (long press on touch) jumps to its line in the text; Alt+click in the text brings the node into view."
},
es: {
@@ -1234,12 +1238,13 @@ const I18N = {
empty:"Aún no hay estructura — simplemente empieza a escribir.", ghost:"…",
mixedWarn:"Línea {line}: bajo «{label}» se mezclan - y | — se representa según el primer hijo.",
xorConflictWarn:"Línea {line}: «{label}» es otra alternativa realizada — un grupo = permite exactamente una.",
+ duplicateIdWarn:"Línea {line}: la ID #{id} ya está asignada (línea {firstLine}).",
st_idee:"idea", st_geplant:"planificado", st_arbeit:"en curso", st_durchstich:"prototipo funcional",
st_fertig:"terminado", st_prod:"en producción", st_highrisk:"alto riesgo", st_verworfen:"descartado",
unknownStatusWarn:"Línea {line}: código de estado desconocido «{code}» — mostrado como neutral.",
sourceLoadWarn:"No se pudo cargar «{url}» ({error}). El archivo debe ser accesible por http(s) y permitir CORS (Access-Control-Allow-Origin).",
sourceTimeoutWarn:"«{url}» no respondió en {seconds} s — se canceló la petición. Etherpad limita la frecuencia de descarga del export (10 veces por 90 s de forma predeterminada); espera un momento y vuelve a cargar.",
- a11yStatus:"Estado: {status}", a11ySize:"Esfuerzo: {size}", a11ySizeImplicit:"Esfuerzo: M (asumido)", a11yTags:"Responsable: {names}", a11yOptional:"opcional", a11yFocusMark:"mirar aquí", a11yLink:"con enlace",
+ a11yStatus:"Estado: {status}", a11ySize:"Esfuerzo: {size}", a11ySizeImplicit:"Esfuerzo: M (asumido)", a11yTags:"Responsable: {names}", a11yId:"ID: #{id}", a11yOptional:"opcional", a11yFocusMark:"mirar aquí", a11yLink:"con enlace",
hint_indent:"La sangría (2 espacios o un tabulador) define la jerarquía.",
hint_all:"subtarea, todas obligatorias", hint_any:"alternativa, elige una",
hint_xor:"alternativa, exactamente una",
@@ -1251,6 +1256,7 @@ const I18N = {
hint_break:"A partir de (M): sigue desglosando — si falta el desglose, aparece un marcador de posición en el diagrama.",
hint_comment:"Comentarios con %% — línea completa o al final de la línea.",
hint_people:"Personas con @nombre — aparecen abajo a la derecha del nodo.",
+ hint_id:"ID de nodo con #nombre — visible en el tooltip del nodo.",
hint_jump:"Alt+clic en un nodo (pulsación larga en táctil) salta a su línea en el texto; Alt+clic en el texto trae el nodo a la vista."
},
fr: {
@@ -1300,12 +1306,13 @@ const I18N = {
empty:"Pas encore de structure — commencez à taper.", ghost:"…",
mixedWarn:"Ligne {line} : sous « {label} », - et | sont mélangés — rendu selon le premier enfant.",
xorConflictWarn:"Ligne {line} : « {label} » est une alternative réalisée de plus — un groupe = n’en autorise qu’une seule.",
+ duplicateIdWarn:"Ligne {line} : l’ID #{id} est déjà attribué (ligne {firstLine}).",
st_idee:"idée", st_geplant:"planifié", st_arbeit:"en cours", st_durchstich:"squelette fonctionnel",
st_fertig:"terminé", st_prod:"en production", st_highrisk:"risque élevé", st_verworfen:"abandonné",
unknownStatusWarn:"Ligne {line} : code de statut inconnu « {code} » — affiché comme neutre.",
sourceLoadWarn:"Impossible de charger « {url} » ({error}). Le fichier doit être accessible en http(s) et autoriser CORS (Access-Control-Allow-Origin).",
sourceTimeoutWarn:"« {url} » n’a pas répondu en {seconds} s — la requête a été interrompue. Etherpad limite la fréquence de récupération de l’export (10 fois par 90 s par défaut) ; attends un instant, puis recharge.",
- a11yStatus:"Statut : {status}", a11ySize:"Effort : {size}", a11ySizeImplicit:"Effort : M (supposé)", a11yTags:"Responsable : {names}", a11yOptional:"facultatif", a11yFocusMark:"regarder ici", a11yLink:"avec lien",
+ a11yStatus:"Statut : {status}", a11ySize:"Effort : {size}", a11ySizeImplicit:"Effort : M (supposé)", a11yTags:"Responsable : {names}", a11yId:"ID : #{id}", a11yOptional:"facultatif", a11yFocusMark:"regarder ici", a11yLink:"avec lien",
hint_indent:"L'indentation (2 espaces ou une tabulation) définit la hiérarchie.",
hint_all:"sous-tâche, toutes requises", hint_any:"alternative, en choisir une",
hint_xor:"alternative, exactement une",
@@ -1317,6 +1324,7 @@ const I18N = {
hint_break:"À partir de (M) : décomposer davantage — si la décomposition manque, un espace réservé apparaît dans le diagramme.",
hint_comment:"Commentaires avec %% — ligne entière ou en fin de ligne.",
hint_people:"Personnes avec @nom — affichées en bas à droite du nœud.",
+ hint_id:"ID de nœud avec #nom — visible dans l’infobulle du nœud.",
hint_jump:"Alt+clic sur un nœud (appui long sur tactile) saute à sa ligne dans le texte ; Alt+clic dans le texte amène le nœud à l’écran."
},
pl: {
@@ -1366,12 +1374,13 @@ const I18N = {
empty:"Brak struktury — zacznij pisać.", ghost:"…",
mixedWarn:"Wiersz {line}: pod „{label}” mieszają się - i | — renderowane według pierwszego dziecka.",
xorConflictWarn:"Wiersz {line}: „{label}” to kolejna zrealizowana alternatywa — grupa = dopuszcza dokładnie jedną.",
+ duplicateIdWarn:"Wiersz {line}: ID #{id} jest już zajęte (wiersz {firstLine}).",
st_idee:"pomysł", st_geplant:"zaplanowane", st_arbeit:"w toku", st_durchstich:"działający szkielet",
st_fertig:"gotowe", st_prod:"w produkcji", st_highrisk:"wysokie ryzyko", st_verworfen:"odrzucone",
unknownStatusWarn:"Wiersz {line}: nieznany znak statusu „{code}” — pokazany jako neutralny.",
sourceLoadWarn:"Nie udało się wczytać „{url}” ({error}). Plik musi być dostępny przez http(s) i zezwalać na CORS (Access-Control-Allow-Origin).",
sourceTimeoutWarn:"„{url}” nie odpowiedział w ciągu {seconds} s — żądanie przerwano. Etherpad ogranicza częstość pobierania eksportu (domyślnie 10 razy na 90 s); odczekaj chwilę i wczytaj ponownie.",
- a11yStatus:"Status: {status}", a11ySize:"Nakład: {size}", a11ySizeImplicit:"Nakład: M (założony)", a11yTags:"Przypisano: {names}", a11yOptional:"opcjonalny", a11yFocusMark:"spójrz tutaj", a11yLink:"z linkiem",
+ a11yStatus:"Status: {status}", a11ySize:"Nakład: {size}", a11ySizeImplicit:"Nakład: M (założony)", a11yTags:"Przypisano: {names}", a11yId:"ID: #{id}", a11yOptional:"opcjonalny", a11yFocusMark:"spójrz tutaj", a11yLink:"z linkiem",
hint_indent:"Wcięcie (2 spacje lub tabulator) definiuje hierarchię.",
hint_all:"podzadanie, wszystkie wymagane", hint_any:"alternatywa, wybierz jedną",
hint_xor:"alternatywa, dokładnie jedna",
@@ -1383,6 +1392,7 @@ const I18N = {
hint_break:"Od (M): dziel dalej — gdy brakuje podziału, w diagramie pojawia się symbol zastępczy.",
hint_comment:"Komentarze z %% — cały wiersz lub na końcu wiersza.",
hint_people:"Osoby z @nazwa — pokazywane w prawym dolnym rogu węzła.",
+ hint_id:"ID węzła przez #nazwa — widoczne w podpowiedzi węzła.",
hint_jump:"Alt+kliknięcie węzła (długie naciśnięcie na dotyku) przechodzi do jego wiersza w tekście; Alt+kliknięcie w tekście pokazuje węzeł na diagramie."
},
ru: {
@@ -1432,12 +1442,13 @@ const I18N = {
empty:"Пока нет структуры — просто начните печатать.", ghost:"…",
mixedWarn:"Строка {line}: под «{label}» смешаны - и | — отображается по первому потомку.",
xorConflictWarn:"Строка {line}: «{label}» — ещё одна реализованная альтернатива, а группа = допускает ровно одну.",
+ duplicateIdWarn:"Строка {line}: ID #{id} уже занят (строка {firstLine}).",
st_idee:"идея", st_geplant:"запланировано", st_arbeit:"в работе", st_durchstich:"сквозной прототип",
st_fertig:"готово", st_prod:"в эксплуатации", st_highrisk:"высокий риск", st_verworfen:"отклонено",
unknownStatusWarn:"Строка {line}: неизвестный код статуса «{code}» — показан как нейтральный.",
sourceLoadWarn:"Не удалось загрузить «{url}» ({error}). Файл должен быть доступен по http(s) и разрешать CORS (Access-Control-Allow-Origin).",
sourceTimeoutWarn:"«{url}» не ответил за {seconds} с — запрос прерван. Etherpad ограничивает частоту загрузки экспорта (по умолчанию 10 раз за 90 с); подождите немного и обновите снова.",
- a11yStatus:"Статус: {status}", a11ySize:"Оценка: {size}", a11ySizeImplicit:"Оценка: M (предполагается)", a11yTags:"Ответственные: {names}", a11yOptional:"необязательно", a11yFocusMark:"смотрите здесь", a11yLink:"со ссылкой",
+ a11yStatus:"Статус: {status}", a11ySize:"Оценка: {size}", a11ySizeImplicit:"Оценка: M (предполагается)", a11yTags:"Ответственные: {names}", a11yId:"ID: #{id}", a11yOptional:"необязательно", a11yFocusMark:"смотрите здесь", a11yLink:"со ссылкой",
hint_indent:"Отступ (2 пробела или табуляция) задаёт иерархию.",
hint_all:"подзадача, все обязательны", hint_any:"альтернатива, выберите одну",
hint_xor:"альтернатива, ровно одна",
@@ -1449,6 +1460,7 @@ const I18N = {
hint_break:"С (M): дробите дальше — если декомпозиции нет, в диаграмме появляется заполнитель.",
hint_comment:"Комментарии через %% — вся строка или в конце строки.",
hint_people:"Люди через @имя — показываются справа внизу узла.",
+ hint_id:"ID узла через #имя — виден во всплывающей подсказке узла.",
hint_jump:"Alt+клик по узлу (долгое нажатие на сенсоре) переходит к его строке в тексте; Alt+клик в тексте показывает узел на диаграмме."
},
hi: {
@@ -1498,12 +1510,13 @@ const I18N = {
empty:"अभी कोई संरचना नहीं — बस टाइप करना शुरू करें।", ghost:"…",
mixedWarn:"पंक्ति {line}: „{label}“ के अंतर्गत - और | मिश्रित हैं — पहले चाइल्ड के अनुसार दिखाया गया।",
xorConflictWarn:"पंक्ति {line}: „{label}“ एक और साकार विकल्प है — = समूह में केवल एक की अनुमति है।",
+ duplicateIdWarn:"पंक्ति {line}: आईडी #{id} पहले से प्रयुक्त है (पंक्ति {firstLine})।",
st_idee:"विचार", st_geplant:"नियोजित", st_arbeit:"प्रगति पर", st_durchstich:"कार्यशील ढाँचा",
st_fertig:"पूर्ण", st_prod:"उत्पादन में", st_highrisk:"उच्च जोखिम", st_verworfen:"अस्वीकृत",
unknownStatusWarn:"पंक्ति {line}: अज्ञात स्थिति कोड „{code}“ — तटस्थ रूप में दिखाया गया।",
sourceLoadWarn:"„{url}“ लोड नहीं हो सका ({error})। फ़ाइल http(s) से उपलब्ध होनी चाहिए और CORS की अनुमति देनी चाहिए (Access-Control-Allow-Origin)।",
sourceTimeoutWarn:"„{url}“ ने {seconds} स॰ में उत्तर नहीं दिया — अनुरोध रद्द कर दिया गया। Etherpad सीमित करता है कि एक्सपोर्ट कितनी बार लिया जा सके (डिफ़ॉल्ट रूप से 90 स॰ में 10 बार); कुछ क्षण रुकें, फिर दोबारा लोड करें।",
- a11yStatus:"स्थिति: {status}", a11ySize:"आकार: {size}", a11ySizeImplicit:"आकार: M (अनुमानित)", a11yTags:"जिम्मेदार: {names}", a11yOptional:"वैकल्पिक", a11yFocusMark:"यहाँ देखें", a11yLink:"लिंक सहित",
+ a11yStatus:"स्थिति: {status}", a11ySize:"आकार: {size}", a11ySizeImplicit:"आकार: M (अनुमानित)", a11yTags:"जिम्मेदार: {names}", a11yId:"आईडी: #{id}", a11yOptional:"वैकल्पिक", a11yFocusMark:"यहाँ देखें", a11yLink:"लिंक सहित",
hint_indent:"इंडेंट (2 स्पेस या टैब) पदानुक्रम तय करता है।",
hint_all:"उप-कार्य, सभी आवश्यक", hint_any:"विकल्प, एक चुनें",
hint_xor:"विकल्प, ठीक एक",
@@ -1515,6 +1528,7 @@ const I18N = {
hint_break:"(M) से आगे: और विभाजित करें — विभाजन न होने पर आरेख में प्लेसहोल्डर दिखता है।",
hint_comment:"%% से टिप्पणियाँ — पूरी पंक्ति या पंक्ति के अंत में।",
hint_people:"@नाम से व्यक्ति — नोड के नीचे-दाएँ दिखते हैं।",
+ hint_id:"#नाम से नोड आईडी — नोड के टूलटिप में दिखती है।",
hint_jump:"किसी नोड पर Alt+क्लिक (टच पर लंबा दबाव) टेक्स्ट में उसकी पंक्ति पर ले जाता है; टेक्स्ट में Alt+क्लिक उस नोड को आरेख में दिखाता है।"
},
zh: {
@@ -1564,12 +1578,13 @@ const I18N = {
empty:"还没有结构——直接开始输入吧。", ghost:"…",
mixedWarn:"第 {line} 行:在「{label}」下 - 与 | 混用——按第一个子项渲染。",
xorConflictWarn:"第 {line} 行:「{label}」是又一个已实现的备选项——= 组只允许恰好一个。",
+ duplicateIdWarn:"第 {line} 行:ID #{id} 已被占用(第 {firstLine} 行)。",
st_idee:"想法", st_geplant:"已计划", st_arbeit:"进行中", st_durchstich:"可运行骨架",
st_fertig:"已完成", st_prod:"已上线", st_highrisk:"高风险", st_verworfen:"已放弃",
unknownStatusWarn:"第 {line} 行:未知状态代码“{code}”——显示为中性。",
sourceLoadWarn:"无法加载“{url}”({error})。该文件必须可通过 http(s) 访问并允许 CORS(Access-Control-Allow-Origin)。",
sourceTimeoutWarn:"“{url}” 在 {seconds} 秒内没有响应 — 请求已中止。Etherpad 会限制导出的获取频率(默认每 90 秒 10 次);请稍候再重新加载。",
- a11yStatus:"状态:{status}", a11ySize:"工作量:{size}", a11ySizeImplicit:"工作量:M(假定)", a11yTags:"负责人:{names}", a11yOptional:"可选", a11yFocusMark:"看这里", a11yLink:"含链接",
+ a11yStatus:"状态:{status}", a11ySize:"工作量:{size}", a11ySizeImplicit:"工作量:M(假定)", a11yTags:"负责人:{names}", a11yId:"ID:#{id}", a11yOptional:"可选", a11yFocusMark:"看这里", a11yLink:"含链接",
hint_indent:"缩进(2 个空格或制表符)定义层级。",
hint_all:"子任务,全部必需", hint_any:"备选项,择其一",
hint_xor:"备选项,恰好一个",
@@ -1581,6 +1596,7 @@ const I18N = {
hint_break:"从 (M) 起:继续细分——若缺少细分,图表中会出现占位符。",
hint_comment:"用 %% 注释——整行或行尾。",
hint_people:"用 @姓名 表示人员——显示在节点右下角。",
+ hint_id:"用 #名称 指定节点 ID——显示在节点提示中。",
hint_jump:"Alt+点击节点(触摸屏为长按)可跳转到文本中对应的行;在文本中 Alt+点击则把该节点带入视野。"
},
ja: {
@@ -1630,12 +1646,13 @@ const I18N = {
empty:"まだ構造がありません — 入力を始めてください。", ghost:"…",
mixedWarn:"{line} 行目:「{label}」の下で - と | が混在 — 最初の子に従って表示。",
xorConflictWarn:"{line} 行目:「{label}」も実現済みの選択肢です — = グループで実現できるのは 1 つだけです。",
+ duplicateIdWarn:"{line} 行目:ID #{id} は既に使われています({firstLine} 行目)。",
st_idee:"アイデア", st_geplant:"計画済み", st_arbeit:"作業中", st_durchstich:"ウォーキングスケルトン",
st_fertig:"完了", st_prod:"本番稼働", st_highrisk:"高リスク", st_verworfen:"破棄",
unknownStatusWarn:"{line} 行目: 不明なステータス記号「{code}」— 中立として表示。",
sourceLoadWarn:"「{url}」を読み込めませんでした({error})。ファイルは http(s) でアクセス可能で、CORS(Access-Control-Allow-Origin)を許可する必要があります。",
sourceTimeoutWarn:"「{url}」が {seconds} 秒以内に応答しませんでした — 要求を中止しました。Etherpad はエクスポートの取得回数を制限します(既定で 90 秒あたり 10 回)。少し待ってから再読み込みしてください。",
- a11yStatus:"ステータス: {status}", a11ySize:"規模: {size}", a11ySizeImplicit:"規模: M(想定)", a11yTags:"担当: {names}", a11yOptional:"任意", a11yFocusMark:"ここを見る", a11yLink:"リンクあり",
+ a11yStatus:"ステータス: {status}", a11ySize:"規模: {size}", a11ySizeImplicit:"規模: M(想定)", a11yTags:"担当: {names}", a11yId:"ID: #{id}", a11yOptional:"任意", a11yFocusMark:"ここを見る", a11yLink:"リンクあり",
hint_indent:"インデント(スペース2つまたはタブ)で階層を定義します。",
hint_all:"サブタスク、すべて必須", hint_any:"選択肢、1つを選ぶ",
hint_xor:"選択肢、ちょうど1つ",
@@ -1647,6 +1664,7 @@ const I18N = {
hint_break:"(M) 以上:さらに分解 — 分解がないと図にプレースホルダーが表示されます。",
hint_comment:"%% でコメント — 行全体または行末。",
hint_people:"@名前 で担当者 — ノードの右下に表示されます。",
+ hint_id:"#名前 でノード ID — ノードのツールチップに表示されます。",
hint_jump:"ノードを Alt+クリック(タッチでは長押し)すると、テキストの該当行へ移動します。テキスト内で Alt+クリックすると、そのノードが図の中央に表示されます。"
}
};
@@ -1679,6 +1697,7 @@ function buildHint(){
${esc(t('hint_break'))}!!! ${esc(t('hint_focus'))}