From 15cc5e418c353c530cb2d11c8999a72743209a98 Mon Sep 17 00:00:00 2001 From: mAi Date: Tue, 26 May 2026 11:57:39 +0200 Subject: [PATCH] feat(verfahrensablauf): side-aware column header labels (t-paliad-295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit m/paliad#127 — m's correction to #88. The user-perspective labels "Unsere Seite" / "Gegnerseite" only make sense once the user has picked a side; while side === null (Nicht festgelegt, the default after #120) the column headers fall back to the semantic-neutral pair "Proaktiv" / "Reaktiv". Picking a side re-enables the #88 labels. renderColumnsBody now branches the leftLabel / rightLabel pair on the incoming side. Bucketing primitive untouched: column placement is unchanged, only the column-header text differs. New i18n keys deadlines.col.proactive / deadlines.col.reactive (DE + EN). The label fallback is documented inline in verfahrensablauf-core.ts so a future reader sees why the columns have two header modes. Tests: four renderColumnsBody assertions covering side=null (explicit + default), side=claimant, side=defendant. Existing bucketing tests unchanged. --- frontend/src/client/i18n.ts | 4 ++ .../views/verfahrensablauf-core.test.ts | 71 +++++++++++++++++++ .../src/client/views/verfahrensablauf-core.ts | 27 +++++-- frontend/src/i18n-keys.ts | 2 + 4 files changed, 98 insertions(+), 6 deletions(-) diff --git a/frontend/src/client/i18n.ts b/frontend/src/client/i18n.ts index 66c998f..e674384 100644 --- a/frontend/src/client/i18n.ts +++ b/frontend/src/client/i18n.ts @@ -309,6 +309,8 @@ const translations: Record> = { "deadlines.col.court": "Gericht", "deadlines.col.opponent": "Gegnerseite", "deadlines.col.both": "Beide Parteien", + "deadlines.col.proactive": "Proaktiv", + "deadlines.col.reactive": "Reaktiv", // t-paliad-265 — per-event-card choice popover (Verfahrensablauf timeline) "choices.caret.title": "Optionen für dieses Ereignis", "choices.appellant.title": "Berufung durch …", @@ -3417,6 +3419,8 @@ const translations: Record> = { "deadlines.col.court": "Court", "deadlines.col.opponent": "Opponent Side", "deadlines.col.both": "Both parties", + "deadlines.col.proactive": "Proactive", + "deadlines.col.reactive": "Reactive", // t-paliad-265 — per-event-card choice popover (Verfahrensablauf timeline) "choices.caret.title": "Options for this event", "choices.appellant.title": "Appeal by …", diff --git a/frontend/src/client/views/verfahrensablauf-core.test.ts b/frontend/src/client/views/verfahrensablauf-core.test.ts index 2d9f689..de63f7a 100644 --- a/frontend/src/client/views/verfahrensablauf-core.test.ts +++ b/frontend/src/client/views/verfahrensablauf-core.test.ts @@ -1,8 +1,10 @@ import { describe, expect, test } from "bun:test"; import { type CalculatedDeadline, + type DeadlineResponse, bucketDeadlinesIntoColumns, deadlineCardHtml, + renderColumnsBody, } from "./verfahrensablauf-core"; // Regression tests for the editable→click-to-edit wiring on timeline date @@ -392,4 +394,73 @@ describe("bucketDeadlinesIntoColumns — side+appellant column routing (m/paliad ["Decision"], ]); }); + +}); + +// m's correction in m/paliad#127 (t-paliad-295) reverted half of #88's +// header refresh: the user-perspective labels "Unsere Seite"/"Gegnerseite" +// only make sense once the user has picked a side. While the side is +// still "Nicht festgelegt" (side === null — the default after #120) the +// header falls back to the semantic-neutral "Proaktiv"/"Reaktiv" labels. +// Picking a side re-enables the #88 labels. The bucketing primitive +// itself is unchanged — only the column-header text differs. +describe("renderColumnsBody — side-aware column header labels (m/paliad#127)", () => { + const dlFix = (party: string, name: string, due: string): CalculatedDeadline => ({ + code: name, + name, + nameEN: name, + party, + priority: "mandatory", + ruleRef: "", + dueDate: due, + originalDate: due, + wasAdjusted: false, + isRootEvent: false, + isCourtSet: false, + }); + const data: DeadlineResponse = { + proceedingType: "upc.inf.cfi", + proceedingName: "UPC Verletzungsverfahren", + triggerDate: "2026-01-01", + deadlines: [ + dlFix("claimant", "Klageschrift", "2026-01-01"), + dlFix("defendant", "Klageerwiderung", "2026-04-01"), + ], + }; + + test("side=null renders Proaktiv/Gericht/Reaktiv headers", () => { + const html = renderColumnsBody(data, { side: null }); + expect(html).toContain(">Proaktiv<"); + expect(html).toContain(">Gericht<"); + expect(html).toContain(">Reaktiv<"); + expect(html).not.toContain(">Unsere Seite<"); + expect(html).not.toContain(">Gegnerseite<"); + }); + + test("side=null when opts omitted (default) still renders Proaktiv/Reaktiv", () => { + const html = renderColumnsBody(data); + expect(html).toContain(">Proaktiv<"); + expect(html).toContain(">Reaktiv<"); + }); + + test("side=claimant renders Unsere Seite/Gericht/Gegnerseite headers", () => { + const html = renderColumnsBody(data, { side: "claimant" }); + expect(html).toContain(">Unsere Seite<"); + expect(html).toContain(">Gericht<"); + expect(html).toContain(">Gegnerseite<"); + expect(html).not.toContain(">Proaktiv<"); + expect(html).not.toContain(">Reaktiv<"); + }); + + test("side=defendant renders Unsere Seite/Gegnerseite headers (column swap is bucketing, not labels)", () => { + // The user-perspective labels are picked once a side is set; the + // bucketer still routes defendant filings into the `ours` column when + // side=defendant, so the left column's header truthfully reads + // "Unsere Seite" regardless of which underlying party occupies it. + const html = renderColumnsBody(data, { side: "defendant" }); + expect(html).toContain(">Unsere Seite<"); + expect(html).toContain(">Gegnerseite<"); + expect(html).not.toContain(">Proaktiv<"); + expect(html).not.toContain(">Reaktiv<"); + }); }); diff --git a/frontend/src/client/views/verfahrensablauf-core.ts b/frontend/src/client/views/verfahrensablauf-core.ts index c0a849f..fe1afa4 100644 --- a/frontend/src/client/views/verfahrensablauf-core.ts +++ b/frontend/src/client/views/verfahrensablauf-core.ts @@ -756,14 +756,29 @@ export function renderColumnsBody(data: DeadlineResponse, opts: ColumnsBodyOpts const headerCell = (label: string, cls: string) => `
${escHtml(label)}
`; - // Static labels — "Unsere Seite" is always the left column, regardless - // of which physical party (claimant vs defendant) occupies it. The - // bucketing primitive already routes the user's side into the `ours` - // bucket, so the header truth-fully describes the column contents. + // Column-header labels have two modes (m/paliad#127): + // - side picked → "Unsere Seite" / "Gegnerseite" (the columns + // truthfully describe whose filings sit there, + // because the bucketer routed the user's side into + // `ours`). + // - side === null → "Proaktiv" / "Reaktiv" (semantic-neutral). The + // user-perspective labels would lie here: we don't + // know yet which party is "us", so calling the left + // column "Unsere Seite" presumes a pick the user + // hasn't made. The neutral Proaktiv/Reaktiv pair + // keeps the spatial axis ("who initiates vs who + // responds") legible while the hint chip on the + // page nudges the user to pick a side. + // + // Note: the COLUMN PROJECTION does not change — the bucketing primitive + // still routes claimant→left, defendant→right when side=null (legacy + // claimant-on-the-left fallback). Only the HEADER label changes. + const leftLabel = userSide === null ? t("deadlines.col.proactive") : t("deadlines.col.ours"); + const rightLabel = userSide === null ? t("deadlines.col.reactive") : t("deadlines.col.opponent"); let html = '
'; - html += headerCell(t("deadlines.col.ours"), "fr-col-ours"); + html += headerCell(leftLabel, "fr-col-ours"); html += headerCell(t("deadlines.col.court"), "fr-col-court"); - html += headerCell(t("deadlines.col.opponent"), "fr-col-opponent"); + html += headerCell(rightLabel, "fr-col-opponent"); for (const row of rows) { html += renderCell(row.ours); diff --git a/frontend/src/i18n-keys.ts b/frontend/src/i18n-keys.ts index c991e15..e303f51 100644 --- a/frontend/src/i18n-keys.ts +++ b/frontend/src/i18n-keys.ts @@ -1233,6 +1233,8 @@ export type I18nKey = | "deadlines.col.event_type" | "deadlines.col.opponent" | "deadlines.col.ours" + | "deadlines.col.proactive" + | "deadlines.col.reactive" | "deadlines.col.rule" | "deadlines.col.status" | "deadlines.col.title"