feat: Fehlende Größe aus den Teilpaketen geschätzt statt pauschal M (D66)
Mindestens die größte Größe der zählenden Kinder, ab drei Kindern dieser
Größe eine Stufe mehr (Deckel XXL); disjunktiv die kleinste Alternative,
rekursiv über größenlose Kinder, Blatt bleibt M. Badge, Tooltip und
aria-label zeigen die geschätzte Größe („mindestens {size} angenommen").
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ebc5be407e
commit
bb01045488
@@ -19,6 +19,7 @@ reverse.
|
|||||||
|
|
||||||
## 2026-08-24
|
## 2026-08-24
|
||||||
|
|
||||||
|
- A missing size is now estimated from the sub-packages — at least the largest child size, one step more from three children of that size
|
||||||
- Node titles wrap at thirty-two characters now instead of forty — long titles pulled the diagram wide
|
- Node titles wrap at thirty-two characters now instead of forty — long titles pulled the diagram wide
|
||||||
- Fix: in the vertical layout, the line from a parent to one or two children was torn — the collector rail now always reaches the parent's stub
|
- Fix: in the vertical layout, the line from a parent to one or two children was torn — the collector rail now always reaches the parent's stub
|
||||||
- The legend shows the high-risk status `[!]` and mentions the size check
|
- The legend shows the high-risk status `[!]` and mentions the size check
|
||||||
|
|||||||
@@ -4928,3 +4928,65 @@ bleiben unberührt. Sichtbare Folge: mehr Knoten brechen um (rund 100 Zeichen
|
|||||||
ergeben jetzt vier statt drei Zeilen), dafür wird der breiteste Fächer
|
ergeben jetzt vier statt drei Zeilen), dafür wird der breiteste Fächer
|
||||||
schmaler. Testerwartungen mit dem echten Algorithmus nachgerechnet statt
|
schmaler. Testerwartungen mit dem echten Algorithmus nachgerechnet statt
|
||||||
geschätzt.
|
geschätzt.
|
||||||
|
|
||||||
|
## D66 — Fehlende Größe wird aus den Teilpaketen geschätzt statt pauschal M
|
||||||
|
Gewünscht vom Nutzer: „Die Kostenschätzung für Knoten ohne explizite
|
||||||
|
T-Shirt-Größe soll anhand der Sub-Knoten geschehen. Mindestens die größte
|
||||||
|
T-Shirt-Größe der Sub-Knoten; wenn mehr als 2 diese Größe haben, also ab 3,
|
||||||
|
dann sogar eine T-Shirt-Größe mehr." Die alte D18-Pauschale — fehlende Größe
|
||||||
|
= M — unterschätzte jeden größenlosen Sammelknoten, sobald ein Kind über M
|
||||||
|
lag; die Kinder sagen mehr, als die Pauschale nutzte.
|
||||||
|
|
||||||
|
**Die Regel:** Angenommen wird **mindestens die größte Größe der zählenden
|
||||||
|
Kinder**; tragen **drei oder mehr** Kinder diese größte Größe, eine Stufe
|
||||||
|
mehr (Deckel `XXL`). Ein Knoten ohne Größe und ohne zählende Kinder bleibt
|
||||||
|
beim M-Rückfall — für ein Blatt gibt es nichts abzuleiten, und D18 nannte M
|
||||||
|
selbst schon „die konservative Annahme ‚mindestens M'".
|
||||||
|
|
||||||
|
**Es zählen dieselben Kinder wie beim Größen-Konflikt (§5/D62)** — direkte,
|
||||||
|
verworfene und optionale (`+`) nie, in einer disjunktiven Gruppe (`|`/`=`)
|
||||||
|
nur die **kleinste** Alternative —, mit genau einem Unterschied: Kinder
|
||||||
|
**ohne** Größe zählen hier mit, ihre Größe wird nach derselben Regel
|
||||||
|
**rekursiv** mitgeschätzt. D62 schließt sie aus, weil eine fehlende Größe
|
||||||
|
keine Autoren-Aussage ist und die Konfliktprüfung nur meldet, was sicher
|
||||||
|
ist; hier wird ohnehin geschätzt — sie auszuschließen hieße, drei größenlose
|
||||||
|
Blätter für kostenlos zu halten. Die D62-Zählung zu übernehmen statt eine
|
||||||
|
dritte Regelmenge zu erfinden hält die Doku bei einem Satz: „dieselben
|
||||||
|
Kinder wie beim Größen-Konflikt".
|
||||||
|
|
||||||
|
**Disjunktiv gilt das Minimum, und keine Stufe mehr.** Realisiert wird genau
|
||||||
|
eine Alternative — die kleinste ist der Boden, den jede Wahl mindestens
|
||||||
|
kostet, und drei gleich große Alternativen sind kein dreifacher Aufwand. Ist
|
||||||
|
in der Gruppe etwas realisiert, ist die Wahl getroffen (D61, `chosenPool`):
|
||||||
|
Dann zählt die kleinste der **realisierten**. Bewusst **nicht** die
|
||||||
|
Alternative, die der günstigste Pfad wählt — die Wahl hängt von den Kosten
|
||||||
|
ab und die Kosten hingen dann von der Wahl: ein Zirkel. Das Minimum ist
|
||||||
|
deterministisch und für ein „mindestens" die ehrliche Untergrenze.
|
||||||
|
|
||||||
|
**Nur die Kostenschätzung ändert sich.** Die §5-Semantik bleibt unberührt,
|
||||||
|
die Konfliktprüfung (D62) rechnet weiter nur mit angegebenen Größen, und die
|
||||||
|
Falt-Voreinstellung „ab M abwärts" (D44, `atMostM`) klappt weiterhin nichts
|
||||||
|
ohne Größenangabe zu — dort wäre die Schätzung eine Vermutung, die wie eine
|
||||||
|
Angabe behandelt würde (die D44-Begründung gilt wörtlich weiter).
|
||||||
|
|
||||||
|
**Das invertierte Badge zeigt jetzt die geschätzte Größe** statt immer „M";
|
||||||
|
Tooltip und `aria-label` sagen „mindestens {size} angenommen". Die beiden
|
||||||
|
vorhandenen i18n-Schlüssel wurden **parameterisiert** statt verdoppelt: Ein
|
||||||
|
Text mit „mindestens {size}" stimmt für die Ableitung wie für den
|
||||||
|
M-Rückfall — kein neuer Schlüssel in neun Sprachen für dieselbe Aussage.
|
||||||
|
|
||||||
|
**Memoisiert per WeakMap**, nicht am Knotenobjekt und nicht ungecacht:
|
||||||
|
`computeCheapPlan` (D42) ruft `ownCost` je Suchbelegung über die ganze
|
||||||
|
nötige Menge — eine ungecachte Rekursion wäre O(n²) je Belegung, bei 20 000
|
||||||
|
Belegungen zu viel. Der Parse-Baum wird bei jedem Tastendruck neu gebaut,
|
||||||
|
der Cache kann also nie veralten, und die WeakMap gibt alte Bäume von
|
||||||
|
selbst frei.
|
||||||
|
|
||||||
|
**Nachgemessen:** Der mitgelieferte Plan hat genau **einen** größenlosen
|
||||||
|
Knoten (`#not.people`, ein Blatt → weiterhin M) — Pfadknoten 135 und
|
||||||
|
Stationen 27 unverändert, 0 Warnungen. Das kanonische Beispiel (§10) ändert
|
||||||
|
sich nicht (seine größenlosen Knoten sind Blätter); alle Snapshots bleiben
|
||||||
|
stehen. 424 Tests, davon 19 neue in `tests/assumed.test.js`. Gegenprobe per
|
||||||
|
Mutation: Stufe-ab-drei entfernt → genau die drei danach benannten
|
||||||
|
Zusicherungen fallen; disjunktiv Maximum statt Minimum → genau eine; alte
|
||||||
|
M-Pauschale in `ownCost` zurückgebaut → genau die zwei Kosten-Tests.
|
||||||
|
|||||||
+17
-7
@@ -603,8 +603,17 @@ werden die für die günstigste Realisierung **nötigen** Knoten:
|
|||||||
entbehrlich (§3). Siehe D61.
|
entbehrlich (§3). Siehe D61.
|
||||||
- Verworfene Knoten zählen nie mit (unabhängig vom „verworfene einblenden"-
|
- Verworfene Knoten zählen nie mit (unabhängig vom „verworfene einblenden"-
|
||||||
Filter).
|
Filter).
|
||||||
- **Fehlende Größe wird als `M` gewertet** (nur für diese Kostenschätzung; die
|
- **Fehlende Größe wird aus den Teilpaketen geschätzt** (nur für diese
|
||||||
SPEC-Semantik der Größen in §5 bleibt unberührt).
|
Kostenschätzung; die SPEC-Semantik der Größen in §5 bleibt unberührt):
|
||||||
|
Angenommen wird **mindestens die größte Größe der zählenden Kinder**;
|
||||||
|
tragen **drei oder mehr** Kinder diese größte Größe, eine Stufe mehr
|
||||||
|
(Deckel: `XXL`). Es zählen dieselben Kinder wie beim Größen-Konflikt (§5) —
|
||||||
|
nur die direkten, verworfene und optionale (`+`) nie, in einer disjunktiven
|
||||||
|
Gruppe (`|`/`=`) nur die **kleinste** Alternative (ist dort etwas realisiert,
|
||||||
|
§3, die kleinste der realisierten) —, mit einem Unterschied: Kinder **ohne**
|
||||||
|
Größe zählen hier mit, ihre Größe wird nach derselben Regel **rekursiv**
|
||||||
|
mitgeschätzt — geschätzt wird ohnehin. Ein Knoten ohne Größe und ohne
|
||||||
|
zählende Kinder wird weiterhin als `M` gewertet.
|
||||||
|
|
||||||
**Mit Abhängigkeiten (§1) zählt die Dependency Closure.** Jeder nötige Knoten
|
**Mit Abhängigkeiten (§1) zählt die Dependency Closure.** Jeder nötige Knoten
|
||||||
zieht seine `:#…`-Ziele samt deren Realisierung in die nötige Menge; gemeinsam
|
zieht seine `:#…`-Ziele samt deren Realisierung in die nötige Menge; gemeinsam
|
||||||
@@ -637,10 +646,11 @@ Alternative behalten ihre Farbe. Blass heißt damit einheitlich „hier ist nich
|
|||||||
getan und wird auch nichts getan". Maßgeblich ist der **intrinsische** Status
|
getan und wird auch nichts getan". Maßgeblich ist der **intrinsische** Status
|
||||||
(die eigene Box), gefärbt wird weiterhin nach dem effektiven (oben) — ein von
|
(die eigene Box), gefärbt wird weiterhin nach dem effektiven (oben) — ein von
|
||||||
Abhängigkeiten zurückgehaltenes `[x]` steht also in voller Stärke in seiner
|
Abhängigkeiten zurückgehaltenes `[x]` steht also in voller Stärke in seiner
|
||||||
effektiven Farbe. Wo die Größe **implizit** als `M` angenommen wird,
|
effektiven Farbe. Wo die Größe **implizit** angenommen wird (aus den
|
||||||
zeigt der Knoten ein **invertiertes** Größen-Badge (weiß mit petrolfarbenem
|
Teilpaketen geschätzt bzw. `M`, oben), zeigt der Knoten die angenommene Größe
|
||||||
Rand/Text statt gefüllt) mit erläuterndem Tooltip — an einem **erledigten**
|
als **invertiertes** Größen-Badge (weiß mit petrolfarbenem Rand/Text statt
|
||||||
Knoten entfällt es, dort wird keine Kostenannahme mehr getroffen.
|
gefüllt) mit erläuterndem Tooltip — an einem **erledigten** Knoten entfällt
|
||||||
|
es, dort wird keine Kostenannahme mehr getroffen.
|
||||||
|
|
||||||
Zusätzlich fädelt eine **gestrichelte, geschwungene Petrol-Linie** durch die
|
Zusätzlich fädelt eine **gestrichelte, geschwungene Petrol-Linie** durch die
|
||||||
**offenen Endknoten** des Pfads (Katmull-Rom-Spline in Dokument-Reihenfolge,
|
**offenen Endknoten** des Pfads (Katmull-Rom-Spline in Dokument-Reihenfolge,
|
||||||
@@ -1070,7 +1080,7 @@ Das Diagramm wird aus der Live-Geometrie in ein eigenständiges SVG (nur Formen
|
|||||||
### Barrierefreiheit
|
### Barrierefreiheit
|
||||||
Die visuell codierten Knoten-Eigenschaften werden für Screenreader in einem
|
Die visuell codierten Knoten-Eigenschaften werden für Screenreader in einem
|
||||||
sprechenden **`aria-label`** je Knoten zusammengefasst — Label, Status, Aufwand
|
sprechenden **`aria-label`** je Knoten zusammengefasst — Label, Status, Aufwand
|
||||||
(inkl. „(angenommen)“ beim impliziten M), Zuständige, ob der Knoten optional
|
(inkl. „(angenommen)“ bei implizit geschätzter Größe), Zuständige, ob der Knoten optional
|
||||||
(§3) und ob er verlinkt ist —, alles in der aktuellen UI-Sprache. Die rein visuellen Beiwerke
|
(§3) und ob er verlinkt ist —, alles in der aktuellen UI-Sprache. Die rein visuellen Beiwerke
|
||||||
(Größen-Badge, Tags, ↗-Pfeil) sind `aria-hidden`, damit sie nicht kryptisch
|
(Größen-Badge, Tags, ↗-Pfeil) sind `aria-hidden`, damit sie nicht kryptisch
|
||||||
doppelt vorgelesen werden. **Alle** Knoten sind fokussierbar (`tabindex="0"`
|
doppelt vorgelesen werden. **Alle** Knoten sind fokussierbar (`tabindex="0"`
|
||||||
|
|||||||
@@ -108,7 +108,11 @@ One node per line. Everything except the label is optional.
|
|||||||
- T-shirt sizes in parentheses: `(XS) (S) (M) (L) (XL) (XXL)`.
|
- T-shirt sizes in parentheses: `(XS) (S) (M) (L) (XL) (XXL)`.
|
||||||
- From `(M)` upward a node **should be decomposed further**; a node ≥ M
|
- From `(M)` upward a node **should be decomposed further**; a node ≥ M
|
||||||
without children gets a placeholder hint in the diagram.
|
without children gets a placeholder hint in the diagram.
|
||||||
- For cost estimation a missing size counts as `M`.
|
- For cost estimation a missing size is **estimated from the sub-packages**:
|
||||||
|
at least the largest size among the counting children (same children as
|
||||||
|
the conflict check below, except unsized children count too — estimated
|
||||||
|
recursively); three or more children at that largest size raise it by one
|
||||||
|
step, capped at `XXL`. Without counting children it stays `M`.
|
||||||
- **Size conflict check:** a given size must fit the direct children. For
|
- **Size conflict check:** a given size must fit the direct children. For
|
||||||
this one check each size is read as a range (lower bounds double, XXL is
|
this one check each size is read as a range (lower bounds double, XXL is
|
||||||
open-ended):
|
open-ended):
|
||||||
|
|||||||
+18
-18
@@ -2146,7 +2146,7 @@ const I18N = {
|
|||||||
cheapTooltip:"Günstigsten Pfad hervorheben – nicht benötigte Alternativen treten zurück",
|
cheapTooltip:"Günstigsten Pfad hervorheben – nicht benötigte Alternativen treten zurück",
|
||||||
leanNextTooltip:"Zur nächsten Station des günstigsten Pfads springen – was als Nächstes dran ist ({n} offen)",
|
leanNextTooltip:"Zur nächsten Station des günstigsten Pfads springen – was als Nächstes dran ist ({n} offen)",
|
||||||
foldSmallTooltip:"Knoten der Größe M und kleiner zuklappen – erneut drücken klappt alle wieder auf",
|
foldSmallTooltip:"Knoten der Größe M und kleiner zuklappen – erneut drücken klappt alle wieder auf",
|
||||||
implicitSizeTooltip:"Keine Größe angegeben – für die Kostenschätzung als M angenommen",
|
implicitSizeTooltip:"Keine Größe angegeben – für die Kostenschätzung mindestens {size} angenommen",
|
||||||
fullscreenTooltip:"Vollbild – Panels nutzen die ganze Fensterbreite",
|
fullscreenTooltip:"Vollbild – Panels nutzen die ganze Fensterbreite",
|
||||||
brandTooltip:"„Werkbaum“ bedeutet so viel wie ‚Werk-Baum‘ — der Baum des Projektstrukturplans (WBS).",
|
brandTooltip:"„Werkbaum“ bedeutet so viel wie ‚Werk-Baum‘ — der Baum des Projektstrukturplans (WBS).",
|
||||||
editorTitle:"Struktur (Text)", diagramTitle:"Diagramm",
|
editorTitle:"Struktur (Text)", diagramTitle:"Diagramm",
|
||||||
@@ -2202,7 +2202,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"Zeile {line}: unbekanntes Statuszeichen „{code}“ — als neutral dargestellt.",
|
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).",
|
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.",
|
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}", a11yId:"ID: #{id}", a11yDeps:"hängt ab von: {ids}", a11yFolded:"eingeklappt, {n} verborgen", a11yEffective:"effektiv: {status}", heldTooltip:"effektiv {eff} — selbst schon {own}, wartet auf Abhängigkeiten", a11yOptional:"optional", a11yFocusMark:"hierhin schauen", a11yLink:"verlinkt",
|
a11yStatus:"Status: {status}", a11ySize:"Aufwand: {size}", a11ySizeImplicit:"Aufwand: mindestens {size} (angenommen)", a11yTags:"Zuständig: {names}", a11yId:"ID: #{id}", a11yDeps:"hängt ab von: {ids}", a11yFolded:"eingeklappt, {n} verborgen", a11yEffective:"effektiv: {status}", heldTooltip:"effektiv {eff} — selbst schon {own}, wartet auf Abhängigkeiten", a11yOptional:"optional", a11yFocusMark:"hierhin schauen", a11yLink:"verlinkt",
|
||||||
hint_indent:"Einrückung (2 Leerzeichen oder Tab) definiert die Hierarchie.",
|
hint_indent:"Einrückung (2 Leerzeichen oder Tab) definiert die Hierarchie.",
|
||||||
hint_all:"Teilpaket, alle erforderlich", hint_any:"Alternative, eine wählen",
|
hint_all:"Teilpaket, alle erforderlich", hint_any:"Alternative, eine wählen",
|
||||||
hint_xor:"Alternative, genau eine",
|
hint_xor:"Alternative, genau eine",
|
||||||
@@ -2251,7 +2251,7 @@ const I18N = {
|
|||||||
cheapTooltip:"Highlight the cheapest path – unneeded alternatives recede",
|
cheapTooltip:"Highlight the cheapest path – unneeded alternatives recede",
|
||||||
leanNextTooltip:"Jump to the next station on the cheapest path – what to tackle next ({n} open)",
|
leanNextTooltip:"Jump to the next station on the cheapest path – what to tackle next ({n} open)",
|
||||||
foldSmallTooltip:"Collapse nodes of size M and smaller – press again to expand them all",
|
foldSmallTooltip:"Collapse nodes of size M and smaller – press again to expand them all",
|
||||||
implicitSizeTooltip:"No size given – assumed as M for the cost estimate",
|
implicitSizeTooltip:"No size given – assumed at least {size} for the cost estimate",
|
||||||
fullscreenTooltip:"Full screen – panels use the full window width",
|
fullscreenTooltip:"Full screen – panels use the full window width",
|
||||||
brandTooltip:"“Werkbaum” means roughly ‘work tree’ — the tree of the work breakdown structure (WBS).",
|
brandTooltip:"“Werkbaum” means roughly ‘work tree’ — the tree of the work breakdown structure (WBS).",
|
||||||
editorTitle:"Structure (text)", diagramTitle:"Diagram",
|
editorTitle:"Structure (text)", diagramTitle:"Diagram",
|
||||||
@@ -2306,7 +2306,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"Line {line}: unknown status code “{code}” — shown as neutral.",
|
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).",
|
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.",
|
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}", a11yId:"ID: #{id}", a11yDeps:"depends on: {ids}", a11yFolded:"collapsed, {n} hidden", a11yEffective:"effective: {status}", heldTooltip:"effectively {eff} — itself already {own}, waiting on dependencies", a11yOptional:"optional", a11yFocusMark:"look here", a11yLink:"has link",
|
a11yStatus:"Status: {status}", a11ySize:"Effort: {size}", a11ySizeImplicit:"Effort: at least {size} (assumed)", a11yTags:"Assigned: {names}", a11yId:"ID: #{id}", a11yDeps:"depends on: {ids}", a11yFolded:"collapsed, {n} hidden", a11yEffective:"effective: {status}", heldTooltip:"effectively {eff} — itself already {own}, waiting on dependencies", a11yOptional:"optional", a11yFocusMark:"look here", a11yLink:"has link",
|
||||||
hint_indent:"Indentation (2 spaces or a tab) defines the hierarchy.",
|
hint_indent:"Indentation (2 spaces or a tab) defines the hierarchy.",
|
||||||
hint_all:"sub-task, all required", hint_any:"alternative, choose one",
|
hint_all:"sub-task, all required", hint_any:"alternative, choose one",
|
||||||
hint_xor:"alternative, exactly one",
|
hint_xor:"alternative, exactly one",
|
||||||
@@ -2355,7 +2355,7 @@ const I18N = {
|
|||||||
cheapTooltip:"Resaltar la ruta más económica: las alternativas no necesarias se atenúan",
|
cheapTooltip:"Resaltar la ruta más económica: las alternativas no necesarias se atenúan",
|
||||||
leanNextTooltip:"Ir a la siguiente estación de la ruta más económica: lo próximo que toca ({n} pendientes)",
|
leanNextTooltip:"Ir a la siguiente estación de la ruta más económica: lo próximo que toca ({n} pendientes)",
|
||||||
foldSmallTooltip:"Plegar los nodos de talla M o menor: pulsa de nuevo para desplegarlos todos",
|
foldSmallTooltip:"Plegar los nodos de talla M o menor: pulsa de nuevo para desplegarlos todos",
|
||||||
implicitSizeTooltip:"Sin tamaño indicado: se asume M para el cálculo de costes",
|
implicitSizeTooltip:"Sin tamaño indicado: se asume al menos {size} para el cálculo de costes",
|
||||||
fullscreenTooltip:"Pantalla completa – los paneles usan todo el ancho de la ventana",
|
fullscreenTooltip:"Pantalla completa – los paneles usan todo el ancho de la ventana",
|
||||||
brandTooltip:"«Werkbaum» significa algo así como ‘árbol de trabajo’ — el árbol de la estructura de desglose del trabajo (EDT).",
|
brandTooltip:"«Werkbaum» significa algo así como ‘árbol de trabajo’ — el árbol de la estructura de desglose del trabajo (EDT).",
|
||||||
editorTitle:"Estructura (texto)", diagramTitle:"Diagrama",
|
editorTitle:"Estructura (texto)", diagramTitle:"Diagrama",
|
||||||
@@ -2410,7 +2410,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"Línea {line}: código de estado desconocido «{code}» — mostrado como neutral.",
|
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).",
|
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.",
|
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}", a11yId:"ID: #{id}", a11yDeps:"depende de: {ids}", a11yFolded:"plegado, {n} ocultos", a11yEffective:"efectivo: {status}", heldTooltip:"efectivamente {eff} — por sí mismo ya {own}, espera dependencias", a11yOptional:"opcional", a11yFocusMark:"mirar aquí", a11yLink:"con enlace",
|
a11yStatus:"Estado: {status}", a11ySize:"Esfuerzo: {size}", a11ySizeImplicit:"Esfuerzo: al menos {size} (asumido)", a11yTags:"Responsable: {names}", a11yId:"ID: #{id}", a11yDeps:"depende de: {ids}", a11yFolded:"plegado, {n} ocultos", a11yEffective:"efectivo: {status}", heldTooltip:"efectivamente {eff} — por sí mismo ya {own}, espera dependencias", a11yOptional:"opcional", a11yFocusMark:"mirar aquí", a11yLink:"con enlace",
|
||||||
hint_indent:"La sangría (2 espacios o un tabulador) define la jerarquía.",
|
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_all:"subtarea, todas obligatorias", hint_any:"alternativa, elige una",
|
||||||
hint_xor:"alternativa, exactamente una",
|
hint_xor:"alternativa, exactamente una",
|
||||||
@@ -2459,7 +2459,7 @@ const I18N = {
|
|||||||
cheapTooltip:"Mettre en évidence le chemin le moins coûteux – les alternatives inutiles s'estompent",
|
cheapTooltip:"Mettre en évidence le chemin le moins coûteux – les alternatives inutiles s'estompent",
|
||||||
leanNextTooltip:"Aller à la station suivante du chemin le moins coûteux – la prochaine chose à faire ({n} en attente)",
|
leanNextTooltip:"Aller à la station suivante du chemin le moins coûteux – la prochaine chose à faire ({n} en attente)",
|
||||||
foldSmallTooltip:"Replier les nœuds de taille M et moins – appuyez à nouveau pour tout déplier",
|
foldSmallTooltip:"Replier les nœuds de taille M et moins – appuyez à nouveau pour tout déplier",
|
||||||
implicitSizeTooltip:"Aucune taille indiquée – considérée comme M pour l'estimation des coûts",
|
implicitSizeTooltip:"Aucune taille indiquée – au moins {size} supposé pour l'estimation des coûts",
|
||||||
fullscreenTooltip:"Plein écran – les panneaux occupent toute la largeur de la fenêtre",
|
fullscreenTooltip:"Plein écran – les panneaux occupent toute la largeur de la fenêtre",
|
||||||
brandTooltip:"« Werkbaum » signifie à peu près « arbre de travail » — l’arbre de l’organigramme des tâches (WBS).",
|
brandTooltip:"« Werkbaum » signifie à peu près « arbre de travail » — l’arbre de l’organigramme des tâches (WBS).",
|
||||||
editorTitle:"Structure (texte)", diagramTitle:"Diagramme",
|
editorTitle:"Structure (texte)", diagramTitle:"Diagramme",
|
||||||
@@ -2514,7 +2514,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"Ligne {line} : code de statut inconnu « {code} » — affiché comme neutre.",
|
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).",
|
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.",
|
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}", a11yId:"ID : #{id}", a11yDeps:"dépend de : {ids}", a11yFolded:"replié, {n} masqués", a11yEffective:"effectif : {status}", heldTooltip:"effectivement {eff} — lui-même déjà {own}, en attente de dépendances", a11yOptional:"facultatif", a11yFocusMark:"regarder ici", a11yLink:"avec lien",
|
a11yStatus:"Statut : {status}", a11ySize:"Effort : {size}", a11ySizeImplicit:"Effort : au moins {size} (supposé)", a11yTags:"Responsable : {names}", a11yId:"ID : #{id}", a11yDeps:"dépend de : {ids}", a11yFolded:"replié, {n} masqués", a11yEffective:"effectif : {status}", heldTooltip:"effectivement {eff} — lui-même déjà {own}, en attente de dépendances", a11yOptional:"facultatif", a11yFocusMark:"regarder ici", a11yLink:"avec lien",
|
||||||
hint_indent:"L'indentation (2 espaces ou une tabulation) définit la hiérarchie.",
|
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_all:"sous-tâche, toutes requises", hint_any:"alternative, en choisir une",
|
||||||
hint_xor:"alternative, exactement une",
|
hint_xor:"alternative, exactement une",
|
||||||
@@ -2563,7 +2563,7 @@ const I18N = {
|
|||||||
cheapTooltip:"Wyróżnij najtańszą ścieżkę – niepotrzebne alternatywy są przygaszone",
|
cheapTooltip:"Wyróżnij najtańszą ścieżkę – niepotrzebne alternatywy są przygaszone",
|
||||||
leanNextTooltip:"Przejdź do następnej stacji najtańszej ścieżki – co dalej ({n} otwartych)",
|
leanNextTooltip:"Przejdź do następnej stacji najtańszej ścieżki – co dalej ({n} otwartych)",
|
||||||
foldSmallTooltip:"Zwiń węzły o rozmiarze M i mniejsze – naciśnij ponownie, aby rozwinąć wszystkie",
|
foldSmallTooltip:"Zwiń węzły o rozmiarze M i mniejsze – naciśnij ponownie, aby rozwinąć wszystkie",
|
||||||
implicitSizeTooltip:"Nie podano rozmiaru – przyjęto M do szacowania kosztów",
|
implicitSizeTooltip:"Nie podano rozmiaru – do szacowania kosztów przyjęto co najmniej {size}",
|
||||||
fullscreenTooltip:"Pełny ekran – panele wykorzystują całą szerokość okna",
|
fullscreenTooltip:"Pełny ekran – panele wykorzystują całą szerokość okna",
|
||||||
brandTooltip:"„Werkbaum” znaczy mniej więcej ‚drzewo pracy’ — drzewo struktury podziału pracy (WBS).",
|
brandTooltip:"„Werkbaum” znaczy mniej więcej ‚drzewo pracy’ — drzewo struktury podziału pracy (WBS).",
|
||||||
editorTitle:"Struktura (tekst)", diagramTitle:"Diagram",
|
editorTitle:"Struktura (tekst)", diagramTitle:"Diagram",
|
||||||
@@ -2618,7 +2618,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"Wiersz {line}: nieznany znak statusu „{code}” — pokazany jako neutralny.",
|
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).",
|
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.",
|
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}", a11yId:"ID: #{id}", a11yDeps:"zależy od: {ids}", a11yFolded:"zwinięte, ukrytych: {n}", a11yEffective:"efektywnie: {status}", heldTooltip:"efektywnie {eff} — sam już {own}, czeka na zależności", a11yOptional:"opcjonalny", a11yFocusMark:"spójrz tutaj", a11yLink:"z linkiem",
|
a11yStatus:"Status: {status}", a11ySize:"Nakład: {size}", a11ySizeImplicit:"Nakład: co najmniej {size} (założony)", a11yTags:"Przypisano: {names}", a11yId:"ID: #{id}", a11yDeps:"zależy od: {ids}", a11yFolded:"zwinięte, ukrytych: {n}", a11yEffective:"efektywnie: {status}", heldTooltip:"efektywnie {eff} — sam już {own}, czeka na zależności", a11yOptional:"opcjonalny", a11yFocusMark:"spójrz tutaj", a11yLink:"z linkiem",
|
||||||
hint_indent:"Wcięcie (2 spacje lub tabulator) definiuje hierarchię.",
|
hint_indent:"Wcięcie (2 spacje lub tabulator) definiuje hierarchię.",
|
||||||
hint_all:"podzadanie, wszystkie wymagane", hint_any:"alternatywa, wybierz jedną",
|
hint_all:"podzadanie, wszystkie wymagane", hint_any:"alternatywa, wybierz jedną",
|
||||||
hint_xor:"alternatywa, dokładnie jedna",
|
hint_xor:"alternatywa, dokładnie jedna",
|
||||||
@@ -2667,7 +2667,7 @@ const I18N = {
|
|||||||
cheapTooltip:"Выделить самый дешёвый путь — ненужные альтернативы приглушаются",
|
cheapTooltip:"Выделить самый дешёвый путь — ненужные альтернативы приглушаются",
|
||||||
leanNextTooltip:"Перейти к следующей станции самого дешёвого пути — что делать дальше ({n} открыто)",
|
leanNextTooltip:"Перейти к следующей станции самого дешёвого пути — что делать дальше ({n} открыто)",
|
||||||
foldSmallTooltip:"Свернуть узлы размера M и меньше — нажмите ещё раз, чтобы развернуть все",
|
foldSmallTooltip:"Свернуть узлы размера M и меньше — нажмите ещё раз, чтобы развернуть все",
|
||||||
implicitSizeTooltip:"Размер не указан — для оценки затрат принят как M",
|
implicitSizeTooltip:"Размер не указан — для оценки затрат принят не меньше {size}",
|
||||||
fullscreenTooltip:"Полный экран – панели занимают всю ширину окна",
|
fullscreenTooltip:"Полный экран – панели занимают всю ширину окна",
|
||||||
brandTooltip:"«Werkbaum» примерно означает ‚дерево работ’ — дерево структуры декомпозиции работ (СДР).",
|
brandTooltip:"«Werkbaum» примерно означает ‚дерево работ’ — дерево структуры декомпозиции работ (СДР).",
|
||||||
editorTitle:"Структура (текст)", diagramTitle:"Диаграмма",
|
editorTitle:"Структура (текст)", diagramTitle:"Диаграмма",
|
||||||
@@ -2722,7 +2722,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"Строка {line}: неизвестный код статуса «{code}» — показан как нейтральный.",
|
unknownStatusWarn:"Строка {line}: неизвестный код статуса «{code}» — показан как нейтральный.",
|
||||||
sourceLoadWarn:"Не удалось загрузить «{url}» ({error}). Файл должен быть доступен по http(s) и разрешать CORS (Access-Control-Allow-Origin).",
|
sourceLoadWarn:"Не удалось загрузить «{url}» ({error}). Файл должен быть доступен по http(s) и разрешать CORS (Access-Control-Allow-Origin).",
|
||||||
sourceTimeoutWarn:"«{url}» не ответил за {seconds} с — запрос прерван. Etherpad ограничивает частоту загрузки экспорта (по умолчанию 10 раз за 90 с); подождите немного и обновите снова.",
|
sourceTimeoutWarn:"«{url}» не ответил за {seconds} с — запрос прерван. Etherpad ограничивает частоту загрузки экспорта (по умолчанию 10 раз за 90 с); подождите немного и обновите снова.",
|
||||||
a11yStatus:"Статус: {status}", a11ySize:"Оценка: {size}", a11ySizeImplicit:"Оценка: M (предполагается)", a11yTags:"Ответственные: {names}", a11yId:"ID: #{id}", a11yDeps:"зависит от: {ids}", a11yFolded:"свёрнуто, скрыто: {n}", a11yEffective:"фактически: {status}", heldTooltip:"фактически {eff} — сам уже {own}, ждёт зависимости", a11yOptional:"необязательно", a11yFocusMark:"смотрите здесь", a11yLink:"со ссылкой",
|
a11yStatus:"Статус: {status}", a11ySize:"Оценка: {size}", a11ySizeImplicit:"Оценка: не меньше {size} (предполагается)", a11yTags:"Ответственные: {names}", a11yId:"ID: #{id}", a11yDeps:"зависит от: {ids}", a11yFolded:"свёрнуто, скрыто: {n}", a11yEffective:"фактически: {status}", heldTooltip:"фактически {eff} — сам уже {own}, ждёт зависимости", a11yOptional:"необязательно", a11yFocusMark:"смотрите здесь", a11yLink:"со ссылкой",
|
||||||
hint_indent:"Отступ (2 пробела или табуляция) задаёт иерархию.",
|
hint_indent:"Отступ (2 пробела или табуляция) задаёт иерархию.",
|
||||||
hint_all:"подзадача, все обязательны", hint_any:"альтернатива, выберите одну",
|
hint_all:"подзадача, все обязательны", hint_any:"альтернатива, выберите одну",
|
||||||
hint_xor:"альтернатива, ровно одна",
|
hint_xor:"альтернатива, ровно одна",
|
||||||
@@ -2771,7 +2771,7 @@ const I18N = {
|
|||||||
cheapTooltip:"सबसे किफ़ायती पथ को उजागर करें – अनावश्यक विकल्प मंद हो जाते हैं",
|
cheapTooltip:"सबसे किफ़ायती पथ को उजागर करें – अनावश्यक विकल्प मंद हो जाते हैं",
|
||||||
leanNextTooltip:"सबसे किफ़ायती पथ के अगले पड़ाव पर जाएँ – अगला काम ({n} शेष)",
|
leanNextTooltip:"सबसे किफ़ायती पथ के अगले पड़ाव पर जाएँ – अगला काम ({n} शेष)",
|
||||||
foldSmallTooltip:"आकार M और उससे छोटे नोड समेटें – सभी को खोलने के लिए फिर दबाएँ",
|
foldSmallTooltip:"आकार M और उससे छोटे नोड समेटें – सभी को खोलने के लिए फिर दबाएँ",
|
||||||
implicitSizeTooltip:"कोई आकार नहीं दिया गया – लागत अनुमान के लिए M माना गया",
|
implicitSizeTooltip:"कोई आकार नहीं दिया गया – लागत अनुमान के लिए कम से कम {size} माना गया",
|
||||||
fullscreenTooltip:"पूर्ण स्क्रीन – पैनल पूरी विंडो चौड़ाई का उपयोग करते हैं",
|
fullscreenTooltip:"पूर्ण स्क्रीन – पैनल पूरी विंडो चौड़ाई का उपयोग करते हैं",
|
||||||
brandTooltip:"„Werkbaum“ का अर्थ लगभग ‚कार्य-वृक्ष‘ है — कार्य विभाजन संरचना (WBS) का वृक्ष।",
|
brandTooltip:"„Werkbaum“ का अर्थ लगभग ‚कार्य-वृक्ष‘ है — कार्य विभाजन संरचना (WBS) का वृक्ष।",
|
||||||
editorTitle:"संरचना (टेक्स्ट)", diagramTitle:"आरेख",
|
editorTitle:"संरचना (टेक्स्ट)", diagramTitle:"आरेख",
|
||||||
@@ -2826,7 +2826,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"पंक्ति {line}: अज्ञात स्थिति कोड „{code}“ — तटस्थ रूप में दिखाया गया।",
|
unknownStatusWarn:"पंक्ति {line}: अज्ञात स्थिति कोड „{code}“ — तटस्थ रूप में दिखाया गया।",
|
||||||
sourceLoadWarn:"„{url}“ लोड नहीं हो सका ({error})। फ़ाइल http(s) से उपलब्ध होनी चाहिए और CORS की अनुमति देनी चाहिए (Access-Control-Allow-Origin)।",
|
sourceLoadWarn:"„{url}“ लोड नहीं हो सका ({error})। फ़ाइल http(s) से उपलब्ध होनी चाहिए और CORS की अनुमति देनी चाहिए (Access-Control-Allow-Origin)।",
|
||||||
sourceTimeoutWarn:"„{url}“ ने {seconds} स॰ में उत्तर नहीं दिया — अनुरोध रद्द कर दिया गया। Etherpad सीमित करता है कि एक्सपोर्ट कितनी बार लिया जा सके (डिफ़ॉल्ट रूप से 90 स॰ में 10 बार); कुछ क्षण रुकें, फिर दोबारा लोड करें।",
|
sourceTimeoutWarn:"„{url}“ ने {seconds} स॰ में उत्तर नहीं दिया — अनुरोध रद्द कर दिया गया। Etherpad सीमित करता है कि एक्सपोर्ट कितनी बार लिया जा सके (डिफ़ॉल्ट रूप से 90 स॰ में 10 बार); कुछ क्षण रुकें, फिर दोबारा लोड करें।",
|
||||||
a11yStatus:"स्थिति: {status}", a11ySize:"आकार: {size}", a11ySizeImplicit:"आकार: M (अनुमानित)", a11yTags:"जिम्मेदार: {names}", a11yId:"आईडी: #{id}", a11yDeps:"निर्भर: {ids}", a11yFolded:"समेटा हुआ, {n} छिपे", a11yEffective:"प्रभावी: {status}", heldTooltip:"प्रभावी रूप से {eff} — स्वयं {own} है, निर्भरताओं की प्रतीक्षा में", a11yOptional:"वैकल्पिक", a11yFocusMark:"यहाँ देखें", a11yLink:"लिंक सहित",
|
a11yStatus:"स्थिति: {status}", a11ySize:"आकार: {size}", a11ySizeImplicit:"आकार: कम से कम {size} (अनुमानित)", a11yTags:"जिम्मेदार: {names}", a11yId:"आईडी: #{id}", a11yDeps:"निर्भर: {ids}", a11yFolded:"समेटा हुआ, {n} छिपे", a11yEffective:"प्रभावी: {status}", heldTooltip:"प्रभावी रूप से {eff} — स्वयं {own} है, निर्भरताओं की प्रतीक्षा में", a11yOptional:"वैकल्पिक", a11yFocusMark:"यहाँ देखें", a11yLink:"लिंक सहित",
|
||||||
hint_indent:"इंडेंट (2 स्पेस या टैब) पदानुक्रम तय करता है।",
|
hint_indent:"इंडेंट (2 स्पेस या टैब) पदानुक्रम तय करता है।",
|
||||||
hint_all:"उप-कार्य, सभी आवश्यक", hint_any:"विकल्प, एक चुनें",
|
hint_all:"उप-कार्य, सभी आवश्यक", hint_any:"विकल्प, एक चुनें",
|
||||||
hint_xor:"विकल्प, ठीक एक",
|
hint_xor:"विकल्प, ठीक एक",
|
||||||
@@ -2860,7 +2860,7 @@ const I18N = {
|
|||||||
cheapTooltip:"突出显示成本最低的路径——不需要的备选项将淡化",
|
cheapTooltip:"突出显示成本最低的路径——不需要的备选项将淡化",
|
||||||
leanNextTooltip:"跳到成本最低路径的下一站——接下来该做的事(还有 {n} 项)",
|
leanNextTooltip:"跳到成本最低路径的下一站——接下来该做的事(还有 {n} 项)",
|
||||||
foldSmallTooltip:"折叠尺寸 M 及更小的节点——再次点击可全部展开",
|
foldSmallTooltip:"折叠尺寸 M 及更小的节点——再次点击可全部展开",
|
||||||
implicitSizeTooltip:"未指定尺寸——成本估算时按 M 计",
|
implicitSizeTooltip:"未指定尺寸——成本估算时按至少 {size} 计",
|
||||||
ghostTooltip:"从 M 号起,元素应进一步细分。",
|
ghostTooltip:"从 M 号起,元素应进一步细分。",
|
||||||
jumpHint:"Alt+点击:跳转到文本中的该行",
|
jumpHint:"Alt+点击:跳转到文本中的该行",
|
||||||
jumpHintTouch:"长按:跳转到文本中的该行",
|
jumpHintTouch:"长按:跳转到文本中的该行",
|
||||||
@@ -2930,7 +2930,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"第 {line} 行:未知状态代码“{code}”——显示为中性。",
|
unknownStatusWarn:"第 {line} 行:未知状态代码“{code}”——显示为中性。",
|
||||||
sourceLoadWarn:"无法加载“{url}”({error})。该文件必须可通过 http(s) 访问并允许 CORS(Access-Control-Allow-Origin)。",
|
sourceLoadWarn:"无法加载“{url}”({error})。该文件必须可通过 http(s) 访问并允许 CORS(Access-Control-Allow-Origin)。",
|
||||||
sourceTimeoutWarn:"“{url}” 在 {seconds} 秒内没有响应 — 请求已中止。Etherpad 会限制导出的获取频率(默认每 90 秒 10 次);请稍候再重新加载。",
|
sourceTimeoutWarn:"“{url}” 在 {seconds} 秒内没有响应 — 请求已中止。Etherpad 会限制导出的获取频率(默认每 90 秒 10 次);请稍候再重新加载。",
|
||||||
a11yStatus:"状态:{status}", a11ySize:"工作量:{size}", a11ySizeImplicit:"工作量:M(假定)", a11yTags:"负责人:{names}", a11yId:"ID:#{id}", a11yDeps:"依赖:{ids}", a11yFolded:"已折叠,隐藏 {n} 项", a11yEffective:"实际:{status}", heldTooltip:"实际为 {eff}——自身已是 {own},等待依赖完成", a11yOptional:"可选", a11yFocusMark:"看这里", a11yLink:"含链接",
|
a11yStatus:"状态:{status}", a11ySize:"工作量:{size}", a11ySizeImplicit:"工作量:至少 {size}(假定)", a11yTags:"负责人:{names}", a11yId:"ID:#{id}", a11yDeps:"依赖:{ids}", a11yFolded:"已折叠,隐藏 {n} 项", a11yEffective:"实际:{status}", heldTooltip:"实际为 {eff}——自身已是 {own},等待依赖完成", a11yOptional:"可选", a11yFocusMark:"看这里", a11yLink:"含链接",
|
||||||
hint_indent:"缩进(2 个空格或制表符)定义层级。",
|
hint_indent:"缩进(2 个空格或制表符)定义层级。",
|
||||||
hint_all:"子任务,全部必需", hint_any:"备选项,择其一",
|
hint_all:"子任务,全部必需", hint_any:"备选项,择其一",
|
||||||
hint_xor:"备选项,恰好一个",
|
hint_xor:"备选项,恰好一个",
|
||||||
@@ -2964,7 +2964,7 @@ const I18N = {
|
|||||||
cheapTooltip:"最も低コストの経路を強調 – 不要な選択肢は控えめに表示",
|
cheapTooltip:"最も低コストの経路を強調 – 不要な選択肢は控えめに表示",
|
||||||
leanNextTooltip:"最も低コストの経路の次の駅へ移動 – 次にやること(残り {n} 件)",
|
leanNextTooltip:"最も低コストの経路の次の駅へ移動 – 次にやること(残り {n} 件)",
|
||||||
foldSmallTooltip:"サイズ M 以下のノードを折りたたむ – もう一度押すとすべて展開",
|
foldSmallTooltip:"サイズ M 以下のノードを折りたたむ – もう一度押すとすべて展開",
|
||||||
implicitSizeTooltip:"サイズ未指定 – コスト見積もりのため M として扱う",
|
implicitSizeTooltip:"サイズ未指定 – コスト見積もりのため少なくとも {size} として扱う",
|
||||||
ghostTooltip:"サイズ M 以上の要素はさらに分解すべきです。",
|
ghostTooltip:"サイズ M 以上の要素はさらに分解すべきです。",
|
||||||
jumpHint:"Alt+クリック:テキストの該当行へ移動",
|
jumpHint:"Alt+クリック:テキストの該当行へ移動",
|
||||||
jumpHintTouch:"長押し:テキストの該当行へ移動",
|
jumpHintTouch:"長押し:テキストの該当行へ移動",
|
||||||
@@ -3034,7 +3034,7 @@ const I18N = {
|
|||||||
unknownStatusWarn:"{line} 行目: 不明なステータス記号「{code}」— 中立として表示。",
|
unknownStatusWarn:"{line} 行目: 不明なステータス記号「{code}」— 中立として表示。",
|
||||||
sourceLoadWarn:"「{url}」を読み込めませんでした({error})。ファイルは http(s) でアクセス可能で、CORS(Access-Control-Allow-Origin)を許可する必要があります。",
|
sourceLoadWarn:"「{url}」を読み込めませんでした({error})。ファイルは http(s) でアクセス可能で、CORS(Access-Control-Allow-Origin)を許可する必要があります。",
|
||||||
sourceTimeoutWarn:"「{url}」が {seconds} 秒以内に応答しませんでした — 要求を中止しました。Etherpad はエクスポートの取得回数を制限します(既定で 90 秒あたり 10 回)。少し待ってから再読み込みしてください。",
|
sourceTimeoutWarn:"「{url}」が {seconds} 秒以内に応答しませんでした — 要求を中止しました。Etherpad はエクスポートの取得回数を制限します(既定で 90 秒あたり 10 回)。少し待ってから再読み込みしてください。",
|
||||||
a11yStatus:"ステータス: {status}", a11ySize:"規模: {size}", a11ySizeImplicit:"規模: M(想定)", a11yTags:"担当: {names}", a11yId:"ID: #{id}", a11yDeps:"依存先: {ids}", a11yFolded:"折りたたみ中、{n} 件非表示", a11yEffective:"実効: {status}", heldTooltip:"実効では {eff} — 自身は既に {own}、依存待ち", a11yOptional:"任意", a11yFocusMark:"ここを見る", a11yLink:"リンクあり",
|
a11yStatus:"ステータス: {status}", a11ySize:"規模: {size}", a11ySizeImplicit:"規模: 少なくとも {size}(想定)", a11yTags:"担当: {names}", a11yId:"ID: #{id}", a11yDeps:"依存先: {ids}", a11yFolded:"折りたたみ中、{n} 件非表示", a11yEffective:"実効: {status}", heldTooltip:"実効では {eff} — 自身は既に {own}、依存待ち", a11yOptional:"任意", a11yFocusMark:"ここを見る", a11yLink:"リンクあり",
|
||||||
hint_indent:"インデント(スペース2つまたはタブ)で階層を定義します。",
|
hint_indent:"インデント(スペース2つまたはタブ)で階層を定義します。",
|
||||||
hint_all:"サブタスク、すべて必須", hint_any:"選択肢、1つを選ぶ",
|
hint_all:"サブタスク、すべて必須", hint_any:"選択肢、1つを選ぶ",
|
||||||
hint_xor:"選択肢、ちょうど1つ",
|
hint_xor:"選択肢、ちょうど1つ",
|
||||||
|
|||||||
+42
-6
@@ -24,9 +24,9 @@ export function needsBreakdown(n){
|
|||||||
/* Faltung-Voreinstellung „Größe M und kleiner zuklappen" (SPEC §9, D44):
|
/* Faltung-Voreinstellung „Größe M und kleiner zuklappen" (SPEC §9, D44):
|
||||||
Knoten mit ANGEGEBENER Größe bis einschließlich M — offen bleiben also nur
|
Knoten mit ANGEGEBENER Größe bis einschließlich M — offen bleiben also nur
|
||||||
L, XL, XXL. Fehlt die Angabe, wird nichts angenommen: Der günstigste Pfad
|
L, XL, XXL. Fehlt die Angabe, wird nichts angenommen: Der günstigste Pfad
|
||||||
rechnet sie zwar als M (D18), das ist aber eine Kostenannahme des Werkzeugs
|
schätzt sie zwar (`assumedSize`, D18/D66), das ist aber eine Kostenannahme
|
||||||
und keine Aussage des Autors; danach den Baum zuzuklappen hieße, eine
|
des Werkzeugs und keine Aussage des Autors; danach den Baum zuzuklappen
|
||||||
Vermutung wie eine Angabe zu behandeln. */
|
hieße, eine Vermutung wie eine Angabe zu behandeln. */
|
||||||
export function atMostM(n){ return !!n.size && SIZE_RANK[n.size] <= SIZE_RANK.M; }
|
export function atMostM(n){ return !!n.size && SIZE_RANK[n.size] <= SIZE_RANK.M; }
|
||||||
|
|
||||||
/* Sichtbare Kinder: verworfene ausblenden, außer showDiscarded ist gesetzt. */
|
/* Sichtbare Kinder: verworfene ausblenden, außer showDiscarded ist gesetzt. */
|
||||||
@@ -40,7 +40,8 @@ export function visibleChildren(n, showDiscarded){
|
|||||||
any-of und XOR ⇒ nur die günstigste Alternative. „Günstig" = kleinste rekursive
|
any-of und XOR ⇒ nur die günstigste Alternative. „Günstig" = kleinste rekursive
|
||||||
Kosten (eigene Größe + Kinder; any-of das Minimum). Verworfene zählen nie
|
Kosten (eigene Größe + Kinder; any-of das Minimum). Verworfene zählen nie
|
||||||
mit (unabhängig vom Einblenden-Toggle). Gleichstand ⇒ erste. Fehlende
|
mit (unabhängig vom Einblenden-Toggle). Gleichstand ⇒ erste. Fehlende
|
||||||
Größe = M. Ist in einer disjunktiven Gruppe etwas realisiert, wird nur noch
|
Größe wird aus den Teilpaketen geschätzt (`assumedSize`, D66).
|
||||||
|
Ist in einer disjunktiven Gruppe etwas realisiert, wird nur noch
|
||||||
unter den realisierten gewählt (`chosenPool`, D61).
|
unter den realisierten gewählt (`chosenPool`, D61).
|
||||||
Optionale Kinder (`+`, SPEC §3/D29) fallen hier ebenfalls heraus — sie sind
|
Optionale Kinder (`+`, SPEC §3/D29) fallen hier ebenfalls heraus — sie sind
|
||||||
per Definition entbehrlich, also weder Kostenanteil noch Pfadknoten. Da alle
|
per Definition entbehrlich, also weder Kostenanteil noch Pfadknoten. Da alle
|
||||||
@@ -80,8 +81,43 @@ export function chosenPool(kids){
|
|||||||
const real = kids.filter(isRealized);
|
const real = kids.filter(isRealized);
|
||||||
return real.length ? real : kids;
|
return real.length ? real : kids;
|
||||||
}
|
}
|
||||||
/* fehlende Größe wird als M interpretiert; Erledigtes kostet nichts mehr */
|
/* ---------- Geschätzte Größe bei fehlender Angabe (SPEC §9, D66) ----------
|
||||||
export function ownCost(n){ return isDone(n) ? 0 : SIZE_RANK[n.size || 'M'] + 1; }
|
Statt pauschal M (die alte D18-Annahme) wird aus den Teilpaketen geschätzt:
|
||||||
|
MINDESTENS die größte Größe der zählenden Kinder; tragen drei oder mehr
|
||||||
|
Kinder diese größte Größe, eine Stufe mehr (Deckel XXL). Es zählen dieselben
|
||||||
|
Kinder wie beim Größen-Konflikt (§5/D62) — direkte, verworfene und optionale
|
||||||
|
nie, disjunktiv (`|`/`=`) nur die kleinste Alternative (aus `chosenPool`,
|
||||||
|
D61: eine getroffene Wahl gilt) — nur dass Kinder OHNE Größe hier rekursiv
|
||||||
|
mitgeschätzt werden: geschätzt wird ohnehin. Ohne zählende Kinder bleibt es
|
||||||
|
beim M-Rückfall. Memo per WeakMap: `computeCheapPlan` ruft `ownCost` je
|
||||||
|
Suchbelegung über die ganze Menge — ungecacht wäre das O(n²) je Belegung;
|
||||||
|
der Parse-Baum wird bei jedem Tastendruck neu gebaut, der Cache kann also
|
||||||
|
nie veralten. */
|
||||||
|
const SIZE_BY_RANK = ['XS', 'S', 'M', 'L', 'XL', 'XXL'];
|
||||||
|
const ASSUMED = new WeakMap();
|
||||||
|
export function assumedSize(n){
|
||||||
|
if(n.size) return n.size;
|
||||||
|
const memo = ASSUMED.get(n);
|
||||||
|
if(memo) return memo;
|
||||||
|
const kids = n.children.filter(k =>
|
||||||
|
!k.optional && (!k.status || k.status.key !== 'verworfen'));
|
||||||
|
let size = 'M';
|
||||||
|
if(kids.length){
|
||||||
|
if(gateOf(kids) !== 'and'){
|
||||||
|
/* nur eine Alternative wird realisiert — die kleinste ist der Boden */
|
||||||
|
size = SIZE_BY_RANK[Math.min(...chosenPool(kids).map(k => SIZE_RANK[assumedSize(k)]))];
|
||||||
|
} else {
|
||||||
|
const ranks = kids.map(k => SIZE_RANK[assumedSize(k)]);
|
||||||
|
const max = Math.max(...ranks);
|
||||||
|
const atMax = ranks.filter(r => r === max).length;
|
||||||
|
size = SIZE_BY_RANK[Math.min(max + (atMax >= 3 ? 1 : 0), SIZE_RANK.XXL)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSUMED.set(n, size);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
/* fehlende Größe wird geschätzt (assumedSize); Erledigtes kostet nichts mehr */
|
||||||
|
export function ownCost(n){ return isDone(n) ? 0 : SIZE_RANK[assumedSize(n)] + 1; }
|
||||||
export function cheapestCost(n){
|
export function cheapestCost(n){
|
||||||
const kids = pathChildren(n);
|
const kids = pathChildren(n);
|
||||||
let c = ownCost(n);
|
let c = ownCost(n);
|
||||||
|
|||||||
+14
-11
@@ -7,7 +7,7 @@
|
|||||||
opts = {
|
opts = {
|
||||||
t, // i18n-Funktion (key, vars?) -> String
|
t, // i18n-Funktion (key, vars?) -> String
|
||||||
showDiscarded, // verworfene einblenden?
|
showDiscarded, // verworfene einblenden?
|
||||||
cheapPath, // günstigster Pfad aktiv? (steuert das implizite M-Badge)
|
cheapPath, // günstigster Pfad aktiv? (steuert das implizite Größen-Badge)
|
||||||
cheapSet, // Set der nötigen Knoten (leer, wenn Pfad aus)
|
cheapSet, // Set der nötigen Knoten (leer, wenn Pfad aus)
|
||||||
freshSet, // optional: Knoten, die neu in Produktion sind (D28)
|
freshSet, // optional: Knoten, die neu in Produktion sind (D28)
|
||||||
collapsedSet, // optional: eingeklappte Knoten (Faltung, SPEC §9/D38)
|
collapsedSet, // optional: eingeklappte Knoten (Faltung, SPEC §9/D38)
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
// Diskrepanzen (effectiveStatus() in model.js, D39)
|
// Diskrepanzen (effectiveStatus() in model.js, D39)
|
||||||
} */
|
} */
|
||||||
|
|
||||||
import { gateOf, needsBreakdown, visibleChildren, cheapCls, isDone } from './model.js';
|
import { gateOf, needsBreakdown, visibleChildren, cheapCls, isDone, assumedSize } from './model.js';
|
||||||
|
|
||||||
/* Zusatzklassen eines Knotens: günstigster Pfad (D18), „neu in Produktion"
|
/* Zusatzklassen eines Knotens: günstigster Pfad (D18), „neu in Produktion"
|
||||||
gegenüber der zuletzt gesehenen Fassung (D28, `freshSet` optional) und
|
gegenüber der zuletzt gesehenen Fassung (D28, `freshSet` optional) und
|
||||||
@@ -106,7 +106,7 @@ function nodeAria(n, opts, fold){
|
|||||||
const effKey = opts.effStatus ? opts.effStatus.get(n) : undefined;
|
const effKey = opts.effStatus ? opts.effStatus.get(n) : undefined;
|
||||||
if(effKey) parts.push(t('a11yEffective', {status: t('st_' + effKey)}));
|
if(effKey) parts.push(t('a11yEffective', {status: t('st_' + effKey)}));
|
||||||
if(n.size) parts.push(t('a11ySize', {size: n.size}));
|
if(n.size) parts.push(t('a11ySize', {size: n.size}));
|
||||||
else if(cheapPath && !isDone(n)) parts.push(t('a11ySizeImplicit'));
|
else if(cheapPath && !isDone(n)) parts.push(t('a11ySizeImplicit', {size: assumedSize(n)}));
|
||||||
/* Größen-Konflikt (SPEC §5/D62): die Warnfärbung des Badges kommt beim
|
/* Größen-Konflikt (SPEC §5/D62): die Warnfärbung des Badges kommt beim
|
||||||
Screenreader sonst nicht an. */
|
Screenreader sonst nicht an. */
|
||||||
if(n.sizeConflict) parts.push(t('sizeConflictTooltip'));
|
if(n.sizeConflict) parts.push(t('sizeConflictTooltip'));
|
||||||
@@ -172,10 +172,12 @@ function nodeHtml(n, extra, opts, fold){
|
|||||||
? t('heldTooltip', {eff: t('st_' + effKey), own: t('st_' + n.status.key)})
|
? t('heldTooltip', {eff: t('st_' + effKey), own: t('st_' + n.status.key)})
|
||||||
: (n.status ? t('st_' + n.status.key) : ''),
|
: (n.status ? t('st_' + n.status.key) : ''),
|
||||||
n.optional ? t('a11yOptional') : '',
|
n.optional ? t('a11yOptional') : '',
|
||||||
/* Die Kostenannahme „Größe fehlt, gilt als M" (D18) hatte einen
|
/* Die Kostenannahme „Größe fehlt, mindestens … angenommen"
|
||||||
eigenen `title` am Badge — der erschiene neben dem Fenster
|
(D18/D66) hatte einen eigenen `title` am Badge — der
|
||||||
ein zweites Mal. Sie gehört ohnehin zu den Kurz-Fakten. */
|
erschiene neben dem Fenster ein zweites Mal. Sie gehört
|
||||||
(!n.size && cheapPath && !isDone(n)) ? t('implicitSizeTooltip') : '',
|
ohnehin zu den Kurz-Fakten. */
|
||||||
|
(!n.size && cheapPath && !isDone(n))
|
||||||
|
? t('implicitSizeTooltip', {size: assumedSize(n)}) : '',
|
||||||
/* Größen-Konflikt (SPEC §5/D62): das warnfarbene Badge braucht
|
/* Größen-Konflikt (SPEC §5/D62): das warnfarbene Badge braucht
|
||||||
seine Begründung dort, wo man nachsieht. */
|
seine Begründung dort, wo man nachsieht. */
|
||||||
n.sizeConflict ? t('sizeConflictTooltip') : '',
|
n.sizeConflict ? t('sizeConflictTooltip') : '',
|
||||||
@@ -191,15 +193,16 @@ function nodeHtml(n, extra, opts, fold){
|
|||||||
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>`
|
||||||
: '';
|
: '';
|
||||||
/* Das implizite M-Badge macht eine KOSTENANNAHME sichtbar (D18). An einem
|
/* Das implizite Größen-Badge macht eine KOSTENANNAHME sichtbar (D18) —
|
||||||
erledigten Knoten wird keine getroffen — er kostet nichts mehr (D46) —,
|
seit D66 die aus den Teilpaketen geschätzte Größe statt pauschal M. An
|
||||||
dort bleibt es deshalb weg. */
|
einem erledigten Knoten wird keine getroffen — er kostet nichts mehr
|
||||||
|
(D46) —, dort bleibt es deshalb weg. */
|
||||||
const implicitSize = !n.size && cheapPath && !isDone(n);
|
const implicitSize = !n.size && cheapPath && !isDone(n);
|
||||||
/* Größen-Konflikt (SPEC §5/D62): das Badge wechselt auf die Warnfarbe —
|
/* Größen-Konflikt (SPEC §5/D62): das Badge wechselt auf die Warnfarbe —
|
||||||
die Größe selbst bleibt stehen, korrigiert wird nichts. */
|
die Größe selbst bleibt stehen, korrigiert wird nichts. */
|
||||||
const sizeBadge = n.size
|
const sizeBadge = n.size
|
||||||
? `<span class="size${n.sizeConflict ? ' conflict' : ''}" aria-hidden="true">${n.size}</span>`
|
? `<span class="size${n.sizeConflict ? ' conflict' : ''}" aria-hidden="true">${n.size}</span>`
|
||||||
: (implicitSize ? `<span class="size implicit" aria-hidden="true">M</span>` : '');
|
: (implicitSize ? `<span class="size implicit" aria-hidden="true">${assumedSize(n)}</span>` : '');
|
||||||
/* High-Risk: Warndreieck (⚠, Textpräsentation via VS15) an der oberen linken
|
/* High-Risk: Warndreieck (⚠, Textpräsentation via VS15) an der oberen linken
|
||||||
Ecke. aria-hidden — die Information steckt bereits im Status des aria-label. */
|
Ecke. aria-hidden — die Information steckt bereits im Status des aria-label. */
|
||||||
/* Kein eigener `title` mehr: Er zeigte sonst zusätzlich zum Knoten-Fenster
|
/* Kein eigener `title` mehr: Er zeigte sonst zusätzlich zum Knoten-Fenster
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { parse } from '../src/parser.js';
|
||||||
|
import { assumedSize, ownCost, cheapestCost } from '../src/model.js';
|
||||||
|
import { renderTreeHtml } from '../src/render.js';
|
||||||
|
|
||||||
|
/* Geschätzte Größe bei fehlender Angabe (SPEC §9, D66): statt pauschal M wird
|
||||||
|
aus den Teilpaketen geschätzt — mindestens die größte Größe der zählenden
|
||||||
|
Kinder, ab drei Kindern dieser Größe eine Stufe mehr (Deckel XXL). Es zählen
|
||||||
|
dieselben Kinder wie beim Größen-Konflikt (§5/D62), nur dass fehlende
|
||||||
|
Kindgrößen rekursiv mitgeschätzt werden. */
|
||||||
|
|
||||||
|
const roots = txt => parse(txt).roots;
|
||||||
|
const t = (key, vars) => {
|
||||||
|
let s = key;
|
||||||
|
if(vars) for(const k in vars) s += ':' + vars[k];
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
const opts = () => ({ t, showDiscarded: false, cheapPath: true, cheapSet: new Set() });
|
||||||
|
|
||||||
|
describe('assumedSize — Schätzung aus den Teilpaketen (D66)', () => {
|
||||||
|
it('Blatt ohne Größe bleibt M (der alte D18-Rückfall)', () => {
|
||||||
|
expect(assumedSize(roots('[ ] Blatt')[0])).toBe('M');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('explizite Größe gewinnt immer', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern (S)
|
||||||
|
- [ ] Kind (XL)`);
|
||||||
|
expect(assumedSize(n)).toBe('S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mindestens die größte Kindgröße', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] A (L)
|
||||||
|
- [ ] B (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('L');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zwei Kinder der größten Größe: noch keine Stufe mehr', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] A (S)
|
||||||
|
- [ ] B (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drei Kinder der größten Größe: eine Stufe mehr', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] A (S)
|
||||||
|
- [ ] B (S)
|
||||||
|
- [ ] C (S)
|
||||||
|
- [ ] D (XS)`);
|
||||||
|
expect(assumedSize(n)).toBe('M');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Deckel XXL: drei XXL-Kinder bleiben XXL', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] A (XXL)
|
||||||
|
- [ ] B (XXL)
|
||||||
|
- [ ] C (XXL)`);
|
||||||
|
expect(assumedSize(n)).toBe('XXL');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Kinder ohne Größe werden rekursiv mitgeschätzt', () => {
|
||||||
|
/* Das Kind hat selbst 3×M-Kinder ⇒ geschätzt L; der Elternknoten erbt L. */
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] Kind
|
||||||
|
- [ ] a (M)
|
||||||
|
- [ ] b (M)
|
||||||
|
- [ ] c (M)`);
|
||||||
|
expect(assumedSize(n.children[0])).toBe('L');
|
||||||
|
expect(assumedSize(n)).toBe('L');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drei größenlose Blätter zählen als 3×M ⇒ L', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] a
|
||||||
|
- [ ] b
|
||||||
|
- [ ] c`);
|
||||||
|
expect(assumedSize(n)).toBe('L');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('optionale und verworfene Kinder zählen nicht', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] A (S)
|
||||||
|
+ [ ] Zugabe (XXL)
|
||||||
|
- [-] Weg (XXL)`);
|
||||||
|
expect(assumedSize(n)).toBe('S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nur optionale Kinder ⇒ M-Rückfall', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
+ [ ] Zugabe (XL)`);
|
||||||
|
expect(assumedSize(n)).toBe('M');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disjunktive Gruppe: die kleinste Alternative ist der Boden', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
| [ ] A (XL)
|
||||||
|
| [ ] B (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('S');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disjunktiv mit getroffener Wahl (D61): die kleinste der realisierten', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
= [~] A (XL)
|
||||||
|
= [ ] B (S)`);
|
||||||
|
expect(assumedSize(n)).toBe('XL');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drei gleich große Alternativen bleiben ohne Stufe mehr — nur eine wird realisiert', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
| [ ] A (L)
|
||||||
|
| [ ] B (L)
|
||||||
|
| [ ] C (L)`);
|
||||||
|
expect(assumedSize(n)).toBe('L');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ownCost und Pfad rechnen mit der Schätzung', () => {
|
||||||
|
it('ownCost eines größenlosen Elternknotens folgt der Schätzung', () => {
|
||||||
|
const [n] = roots(`[ ] Eltern
|
||||||
|
- [ ] Kind (XL)`);
|
||||||
|
expect(ownCost(n)).toBe(ownCost(roots('[ ] X (XL)')[0]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cheapestCost steigt gegenüber der alten M-Annahme', () => {
|
||||||
|
const derived = cheapestCost(roots(`[ ] Eltern
|
||||||
|
- [ ] Kind (XL)`)[0]);
|
||||||
|
const stated = cheapestCost(roots(`[ ] Eltern (M)
|
||||||
|
- [ ] Kind (XL)`)[0]);
|
||||||
|
expect(derived).toBeGreaterThan(stated);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('am erledigten Knoten wird weiterhin nichts angenommen (Kosten 0)', () => {
|
||||||
|
const [n] = roots(`[x] Eltern
|
||||||
|
- [x] Kind (XL)`);
|
||||||
|
expect(ownCost(n)).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Badge zeigt die geschätzte Größe', () => {
|
||||||
|
it('das invertierte Badge trägt die abgeleitete Größe statt M', () => {
|
||||||
|
const { html } = renderTreeHtml(roots(`[ ] Eltern
|
||||||
|
- [ ] Kind (XL)`), opts());
|
||||||
|
expect(html).toContain('<span class="size implicit" aria-hidden="true">XL</span>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tooltip und aria-label nennen die geschätzte Größe', () => {
|
||||||
|
const { html } = renderTreeHtml(roots(`[ ] Eltern
|
||||||
|
- [ ] Kind (L)`), opts());
|
||||||
|
expect(html).toContain('implicitSizeTooltip:L');
|
||||||
|
expect(html).toContain('a11ySizeImplicit:L');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Blatt ohne Größe zeigt weiterhin M', () => {
|
||||||
|
const { html } = renderTreeHtml(roots('[ ] Blatt'), opts());
|
||||||
|
expect(html).toContain('<span class="size implicit" aria-hidden="true">M</span>');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user