From 48a07ef4ef4909677d701163cc9e313d89d872a3 Mon Sep 17 00:00:00 2001
From: mAi
Date: Wed, 27 May 2026 20:29:05 +0200
Subject: [PATCH] feat(procedures): U3 fold Verfahrensablauf tree + 3-way
detail filter (m/paliad#151)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Mounts the full Verfahrensablauf wizard — proceeding picker, perspective
chooser, date inputs, scenario flag rows, detail-mode toggle, view
toggle, timeline-container — under the /tools/procedures "Verfahren
wählen" tab. Per-rule scenario_flags chips (P0 SSoT) and the
Aufnehmen/Entfernen affordances reach the unified page unchanged since
they're delegated handlers on the timeline-container.
Refactor steps:
- Extracted the wizard body markup into a shared TSX component
(components/VerfahrensablaufBody) used by both verfahrensablauf.tsx
(legacy) and procedures.tsx (unified). U4 will retire the legacy
page; the shared component lets U3 ship without code duplication.
- Lifted the verfahrensablauf.ts DOMContentLoaded body into
initVerfahrensablauf() and re-exported it. The legacy auto-boot
stays in place but skips itself when #procedures-panel-proceeding
is present, so the unified page imports the module without
double-init. procedures.ts calls initVerfahrensablauf() the first
time the proceeding tab activates, gated by a one-shot flag to
preserve module-local selectedType / lastResponse across tab
toggles.
---
frontend/src/client/i18n.ts | 2 -
frontend/src/client/procedures.ts | 19 +-
frontend/src/client/verfahrensablauf.ts | 24 +-
.../src/components/VerfahrensablaufBody.tsx | 293 ++++++++++++++
frontend/src/i18n-keys.ts | 1 -
frontend/src/procedures.tsx | 15 +-
frontend/src/verfahrensablauf.tsx | 356 +-----------------
7 files changed, 348 insertions(+), 362 deletions(-)
create mode 100644 frontend/src/components/VerfahrensablaufBody.tsx
diff --git a/frontend/src/client/i18n.ts b/frontend/src/client/i18n.ts
index 81b328c..6a4b40e 100644
--- a/frontend/src/client/i18n.ts
+++ b/frontend/src/client/i18n.ts
@@ -218,7 +218,6 @@ const translations: Record> = {
"procedures.tab.search": "Direkt suchen",
"procedures.tab.wizard": "Gef\u00fchrt",
"procedures.tab.akte": "Aus Akte",
- "procedures.panel.proceeding.placeholder": "Verfahrenswahl folgt in U3 \u2014 das \u00dcbersichts-Baumdiagramm wird hier eingebettet.",
"procedures.panel.akte.placeholder": "Akten-Einstieg folgt in einem sp\u00e4teren Slice.",
"nav.procedures": "Verfahren & Fristen",
@@ -3429,7 +3428,6 @@ const translations: Record> = {
"procedures.tab.search": "Direct search",
"procedures.tab.wizard": "Guided",
"procedures.tab.akte": "From matter",
- "procedures.panel.proceeding.placeholder": "Pick-proceeding view ships in U3 \u2014 the overview tree mounts here.",
"procedures.panel.akte.placeholder": "Matter entry ships in a later slice.",
"nav.procedures": "Procedures & Deadlines",
diff --git a/frontend/src/client/procedures.ts b/frontend/src/client/procedures.ts
index b933a09..0723a22 100644
--- a/frontend/src/client/procedures.ts
+++ b/frontend/src/client/procedures.ts
@@ -8,8 +8,8 @@
//
// U0 — Skeleton + tab toggling.
// U1 — Direkt suchen mounts Mode A.
-// U2 — Geführt mounts Mode B wizard (this slice).
-// U3 — Verfahren wählen mounts Verfahrensablauf tree + 3-way detail filter.
+// U2 — Geführt mounts Mode B wizard.
+// U3 — Verfahren wählen wires the Verfahrensablauf wizard + detail-mode toggle.
//
// Mode A renders its shell into #fristen-overhaul-root (replacing
// children); Mode B renders into #fristen-overhaul-mode-host; the
@@ -24,6 +24,7 @@ import { initSidebar } from "./sidebar";
import { mountModeA } from "./fristenrechner-mode-a";
import { mountResultView } from "./fristenrechner-result";
import { mountWizard } from "./fristenrechner-wizard";
+import { initVerfahrensablauf } from "./verfahrensablauf";
type ProceduresTab = "proceeding" | "search" | "wizard" | "akte";
@@ -80,6 +81,13 @@ function setActiveTabUI(tab: ProceduresTab): void {
}
}
+// Verfahrensablauf wiring is idempotent-unfriendly (module-local
+// selectedType + lastResponse + listeners that re-bind on every
+// proceeding click). Wire it exactly once per page load; on subsequent
+// activations the existing DOM + listeners are reused so picked
+// proceeding / dates / flags persist across tab switches.
+let verfahrensablaufWired = false;
+
async function activateTab(tab: ProceduresTab): Promise {
setActiveTabUI(tab);
if (tab === "search") {
@@ -92,7 +100,12 @@ async function activateTab(tab: ProceduresTab): Promise {
await mountWizard();
return;
}
- // U3 will mount Verfahrensablauf into proceeding-panel.
+ if (tab === "proceeding") {
+ if (!verfahrensablaufWired) {
+ initVerfahrensablauf();
+ verfahrensablaufWired = true;
+ }
+ }
}
function wireTabs(): void {
diff --git a/frontend/src/client/verfahrensablauf.ts b/frontend/src/client/verfahrensablauf.ts
index dc76c12..3cdc058 100644
--- a/frontend/src/client/verfahrensablauf.ts
+++ b/frontend/src/client/verfahrensablauf.ts
@@ -1011,10 +1011,14 @@ function initPerspectiveControls() {
});
}
-document.addEventListener("DOMContentLoaded", () => {
- initI18n();
- initSidebar();
-
+// initVerfahrensablauf wires the entire Verfahrensablauf wizard against
+// whatever DOM is currently present (proceeding-btn buttons,
+// trigger-date input, flag checkboxes, timeline-container, …).
+// Re-callable on demand: m/paliad#151 mounts this against the
+// /tools/procedures "Verfahren wählen" tab the first time it activates.
+// initI18n() + initSidebar() are NOT included here — both are page-boot
+// concerns owned by whichever entrypoint hosts the wiring.
+export function initVerfahrensablauf(): void {
document.querySelectorAll(".proceeding-btn").forEach((btn) => {
btn.addEventListener("click", () => selectProceeding(btn));
});
@@ -1257,4 +1261,16 @@ document.addEventListener("DOMContentLoaded", () => {
const writeURL = urlProceeding !== "" && !urlHit;
selectProceeding(initialBtn, { writeURL });
}
+}
+
+// Legacy /tools/verfahrensablauf entrypoint auto-boot. The unified
+// /tools/procedures page imports this module too but owns its own boot —
+// the guard checks for the procedures-only #procedures-panel-proceeding
+// element so the auto-boot doesn't fire twice. U4 drops the legacy page
+// + this auto-boot together.
+document.addEventListener("DOMContentLoaded", () => {
+ if (document.getElementById("procedures-panel-proceeding")) return;
+ initI18n();
+ initSidebar();
+ initVerfahrensablauf();
});
diff --git a/frontend/src/components/VerfahrensablaufBody.tsx b/frontend/src/components/VerfahrensablaufBody.tsx
new file mode 100644
index 0000000..4951387
--- /dev/null
+++ b/frontend/src/components/VerfahrensablaufBody.tsx
@@ -0,0 +1,293 @@
+import { h } from "../jsx";
+
+interface ProceedingDef {
+ code: string;
+ i18nKey: string;
+ name: string;
+}
+
+function proceedingBtn(p: ProceedingDef): string {
+ return (
+
+ );
+}
+
+// Slice B1 (m/paliad#124 §18.1): the 3 separate Berufung tiles
+// (upc.apl.merits / upc.apl.cost / upc.apl.order) collapse into ONE
+// unified "Berufung" tile (upc.apl). After picking it, the user
+// selects which decision the appeal is directed AT via the
+// .appeal-target-row chip group below — the engine then filters
+// rules whose applies_to_target contains the picked slug.
+const UPC_TYPES: ProceedingDef[] = [
+ { code: "upc.inf.cfi", i18nKey: "deadlines.upc.inf.cfi", name: "Verletzungsverfahren" },
+ { code: "upc.rev.cfi", i18nKey: "deadlines.upc.rev.cfi", name: "Nichtigkeitsklage" },
+ { code: "upc.ccr.cfi", i18nKey: "deadlines.upc.ccr.cfi", name: "Widerklage auf Nichtigkeit" },
+ { code: "upc.pi.cfi", i18nKey: "deadlines.upc.pi.cfi", name: "Einstw. Maßnahmen" },
+ { code: "upc.apl.unified", i18nKey: "deadlines.upc.apl.unified", name: "Berufung" },
+ { code: "upc.dmgs.cfi", i18nKey: "deadlines.upc.dmgs.cfi", name: "Schadensbemessung" },
+ { code: "upc.disc.cfi", i18nKey: "deadlines.upc.disc.cfi", name: "Bucheinsicht" },
+];
+
+const DE_INF_TYPES: ProceedingDef[] = [
+ { code: "de.inf.lg", i18nKey: "deadlines.de.inf.lg", name: "LG (1. Instanz)" },
+ { code: "de.inf.olg", i18nKey: "deadlines.de.inf.olg", name: "OLG (Berufung)" },
+ { code: "de.inf.bgh", i18nKey: "deadlines.de.inf.bgh", name: "BGH (Revision / NZB)" },
+];
+
+const DE_NULL_TYPES: ProceedingDef[] = [
+ { code: "de.null.bpatg", i18nKey: "deadlines.de.null.bpatg", name: "BPatG (1. Instanz)" },
+ { code: "de.null.bgh", i18nKey: "deadlines.de.null.bgh", name: "BGH (Berufung)" },
+];
+
+const EPA_TYPES: ProceedingDef[] = [
+ { code: "epa.opp.opd", i18nKey: "deadlines.epa.opp.opd", name: "Einspruchsverfahren" },
+ { code: "epa.opp.boa", i18nKey: "deadlines.epa.opp.boa", name: "Beschwerdeverfahren" },
+ { code: "epa.grant.exa", i18nKey: "deadlines.epa.grant.exa", name: "EP-Erteilungsverfahren" },
+];
+
+const DPMA_TYPES: ProceedingDef[] = [
+ { code: "dpma.opp.dpma", i18nKey: "deadlines.dpma.opp.dpma", name: "Einspruch DPMA" },
+ { code: "dpma.appeal.bpatg", i18nKey: "deadlines.dpma.appeal.bpatg", name: "Beschwerde BPatG (DPMA)" },
+ { code: "dpma.appeal.bgh", i18nKey: "deadlines.dpma.appeal.bgh", name: "Rechtsbeschwerde BGH" },
+];
+
+// Shared Verfahrensablauf wizard body. Renders the proceeding picker,
+// perspective + date inputs, scenario flag rows, detail-mode toggle,
+// view toggle, and the timeline-container that client/verfahrensablauf.ts
+// (via initVerfahrensablauf()) wires against. Used by both
+// /tools/verfahrensablauf (legacy) and /tools/procedures (unified).
+export function VerfahrensablaufBody({ todayIso }: { todayIso: string }): string {
+ return (
+
+
+
+ 1
+ Verfahrensart wählen
+
+
+
+
UPC
+
+ {UPC_TYPES.map((p) => proceedingBtn(p))}
+
+
+
+
+
Deutsche Gerichte
+
+
Verletzungsverfahren
+
+ {DE_INF_TYPES.map((p) => proceedingBtn(p))}
+
+
+
+
Nichtigkeitsverfahren
+
+ {DE_NULL_TYPES.map((p) => proceedingBtn(p))}
+
+
+
+
+
+
EPA
+
+ {EPA_TYPES.map((p) => proceedingBtn(p))}
+
+
+
+
+
DPMA
+
+ {DPMA_TYPES.map((p) => proceedingBtn(p))}
+
+
+
+
+ Verfahren:
+ —
+
+
+
+
+
+
+ 2
+ Perspektive und Datum
+
+
+
+
+ Seite:
+
+
+
+
+
+
+
+ Wählen Sie eine Seite, um die Spalten zu fokussieren.
+
+
+
+ Aus Akte:
+ —
+
+
+
+
+ Worauf richtet sich die Berufung?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auslösendes Ereignis:
+ —
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+ Ergebnis
+
+
+
+ Anzeige:
+
+
+
+
+
+
+ Ansicht:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/i18n-keys.ts b/frontend/src/i18n-keys.ts
index 8b1958b..e64e18a 100644
--- a/frontend/src/i18n-keys.ts
+++ b/frontend/src/i18n-keys.ts
@@ -2212,7 +2212,6 @@ export type I18nKey =
| "procedures.filter.search.placeholder"
| "procedures.heading"
| "procedures.panel.akte.placeholder"
- | "procedures.panel.proceeding.placeholder"
| "procedures.subtitle"
| "procedures.tab.akte"
| "procedures.tab.proceeding"
diff --git a/frontend/src/procedures.tsx b/frontend/src/procedures.tsx
index 385fabd..a4bea0f 100644
--- a/frontend/src/procedures.tsx
+++ b/frontend/src/procedures.tsx
@@ -4,6 +4,7 @@ import { PaliadinWidget } from "./components/PaliadinWidget";
import { BottomNav } from "./components/BottomNav";
import { Footer } from "./components/Footer";
import { PWAHead } from "./components/PWAHead";
+import { VerfahrensablaufBody } from "./components/VerfahrensablaufBody";
// U0 — Skeleton for the unified procedural-events tool
// (m/paliad#151, design docs/design-unified-procedural-events-tool-2026-05-27.md).
@@ -22,6 +23,7 @@ import { PWAHead } from "./components/PWAHead";
// later slices mount their UI into. No data wiring.
export function renderProcedures(): string {
+ const today = new Date().toISOString().split("T")[0];
return "" + (
@@ -141,9 +143,16 @@ export function renderProcedures(): string {
Each later slice fills the corresponding host. */}
-
- Verfahrenswahl folgt in U3 — das Übersichts-Baumdiagramm wird hier eingebettet.
-
+ {/* Verfahrensablauf wizard body — shared TSX component
+ used by /tools/verfahrensablauf (legacy) and the
+ unified /tools/procedures page. procedures.ts calls
+ initVerfahrensablauf() on the first activation of
+ this tab, which wires the .proceeding-btn clicks,
+ timeline-container, detail-mode toggle, etc. against
+ the markup. The legacy page's auto-boot is guarded
+ against the procedures-only #procedures-panel-proceeding
+ element so it doesn't fire twice. */}
+
- {p.name}
-
- );
-}
-
-// Slice B1 (m/paliad#124 §18.1): the 3 separate Berufung tiles
-// (upc.apl.merits / upc.apl.cost / upc.apl.order) collapse into ONE
-// unified "Berufung" tile (upc.apl). After picking it, the user
-// selects which decision the appeal is directed AT via the
-// .appeal-target-row chip group below — the engine then filters
-// rules whose applies_to_target contains the picked slug.
-const UPC_TYPES: ProceedingDef[] = [
- { code: "upc.inf.cfi", i18nKey: "deadlines.upc.inf.cfi", name: "Verletzungsverfahren" },
- { code: "upc.rev.cfi", i18nKey: "deadlines.upc.rev.cfi", name: "Nichtigkeitsklage" },
- { code: "upc.ccr.cfi", i18nKey: "deadlines.upc.ccr.cfi", name: "Widerklage auf Nichtigkeit" },
- { code: "upc.pi.cfi", i18nKey: "deadlines.upc.pi.cfi", name: "Einstw. Maßnahmen" },
- { code: "upc.apl.unified", i18nKey: "deadlines.upc.apl.unified", name: "Berufung" },
- { code: "upc.dmgs.cfi", i18nKey: "deadlines.upc.dmgs.cfi", name: "Schadensbemessung" },
- { code: "upc.disc.cfi", i18nKey: "deadlines.upc.disc.cfi", name: "Bucheinsicht" },
-];
-
-// DE proceedings split by type (Verletzung / Nichtigkeit) per m's
-// 2026-05-18 ask. Labels are parallel: (),
-// so a user scanning the picker sees the instance-and-role at a glance
-// without one tile reading "Berufung OLG" and another "Nichtigkeits-
-// verfahren". Sub-group headers convey the type grouping. Combined-
-// timeline behaviour (LG→OLG→BGH as one calc) is filed as m/paliad#41.
-const DE_INF_TYPES: ProceedingDef[] = [
- { code: "de.inf.lg", i18nKey: "deadlines.de.inf.lg", name: "LG (1. Instanz)" },
- { code: "de.inf.olg", i18nKey: "deadlines.de.inf.olg", name: "OLG (Berufung)" },
- { code: "de.inf.bgh", i18nKey: "deadlines.de.inf.bgh", name: "BGH (Revision / NZB)" },
-];
-
-const DE_NULL_TYPES: ProceedingDef[] = [
- { code: "de.null.bpatg", i18nKey: "deadlines.de.null.bpatg", name: "BPatG (1. Instanz)" },
- { code: "de.null.bgh", i18nKey: "deadlines.de.null.bgh", name: "BGH (Berufung)" },
-];
-
-const EPA_TYPES: ProceedingDef[] = [
- { code: "epa.opp.opd", i18nKey: "deadlines.epa.opp.opd", name: "Einspruchsverfahren" },
- { code: "epa.opp.boa", i18nKey: "deadlines.epa.opp.boa", name: "Beschwerdeverfahren" },
- { code: "epa.grant.exa", i18nKey: "deadlines.epa.grant.exa", name: "EP-Erteilungsverfahren" },
-];
-
-const DPMA_TYPES: ProceedingDef[] = [
- { code: "dpma.opp.dpma", i18nKey: "deadlines.dpma.opp.dpma", name: "Einspruch DPMA" },
- { code: "dpma.appeal.bpatg", i18nKey: "deadlines.dpma.appeal.bpatg", name: "Beschwerde BPatG (DPMA)" },
- { code: "dpma.appeal.bgh", i18nKey: "deadlines.dpma.appeal.bgh", name: "Rechtsbeschwerde BGH" },
-];
+// picker + result panel.
+//
+// U3 (m/paliad#151) extracted the wizard body markup into
+// components/VerfahrensablaufBody so /tools/procedures can mount the
+// same DOM under its "Verfahren wählen" tab. U4 will retire this page.
export function renderVerfahrensablauf(): string {
const today = new Date().toISOString().split("T")[0];
@@ -102,294 +47,7 @@ export function renderVerfahrensablauf(): string {
- {/* Verfahrensart picker (single-tile mode — same DOM ids as
- /tools/fristenrechner so the shared renderer module and
- court-picker primitives bind without parameterisation). */}
-
-
-
- 1
- Verfahrensart wählen
-
-
-
-
UPC
-
- {UPC_TYPES.map((p) => proceedingBtn(p))}
-
-
-
-
-
Deutsche Gerichte
-
-
Verletzungsverfahren
-
- {DE_INF_TYPES.map((p) => proceedingBtn(p))}
-
-
-
-
Nichtigkeitsverfahren
-
- {DE_NULL_TYPES.map((p) => proceedingBtn(p))}
-
-
-
-
-
-
EPA
-
- {EPA_TYPES.map((p) => proceedingBtn(p))}
-
-
-
-
-
DPMA
-
- {DPMA_TYPES.map((p) => proceedingBtn(p))}
-
-
-
-
- Verfahren:
- —
-
-
-
-
-
-
- 2
- Perspektive und Datum
-
-
- {/* Perspective strip (t-paliad-250 / m/paliad#81, reordered
- in t-paliad-279 / m/paliad#111). Side defines whose
- perspective the columns project; appellant collapses
- party=both rows for role-swap proceedings (Appeal etc.).
- Moved above .date-input-group because party-side is the
- most-defining input after proceeding-type — without
- side, the column labels can't pick "your filings". Both
- selectors are URL-driven (?side= + ?appellant=) so the
- perspective survives reload and is shareable.
-
- When the page is opened with ?project= and that
- project's our_side is set, side-row renders as a
- read-only chip with an "Andere Seite wählen" override
- link — see client/verfahrensablauf.ts. */}
-
-
- Seite:
-
-
-
-
-
-
- {/* Prompt shown while the user hasn't picked a side
- (m/paliad#120). Hidden by client when side is
- claimant or defendant. Both columns still
- render every rule in this state — picking a
- side just focuses the user's column. */}
-
- Wählen Sie eine Seite, um die Spalten zu fokussieren.
-
-
- {/* Auto-fill chip — populated by the client when a
- ?project= URL resolves a project with our_side
- set. Hidden by default; the radio cluster above is
- hidden whenever this chip is shown. */}
-
- Aus Akte:
- —
-
-
-
- {/* Appeal-target chip row (Slice B1 / m/paliad#124 §18.1).
- Shown only when the unified upc.apl Berufung tile is
- selected; lets the user narrow the timeline to the
- rules whose applies_to_target contains the picked
- decision kind. URL state ?target=. */}
-
- Worauf richtet sich die Berufung?
-
-
-
-
-
-
-
-
- {/* Show-hidden toggle (t-paliad-290 / m/paliad#122).
- Re-surfaces optional cards the user has previously
- marked "Überspringen" via the per-card popover.
- The row hides itself when the projection has no
- hidden cards (handled in client/verfahrensablauf.ts).
- Default OFF; URL state ?show_hidden=1. */}
-
-
-
-
-
-
- {/* Visual divider — keeps the perspective block (most-
- defining inputs after proceeding-type) optically
- separate from the date / court / flag knobs below. */}
-
-
-
-
- {/* Read-only caption labelling the value . Not a
-
-
- Datum:
-
-
-
- Gericht:
-
-
- {/* Proceeding-specific flag rows — mirror /tools/fristenrechner
- so an abstract-browse user can model the same variants
- (CCR, Patentänderung, Verletzungswiderklage,
- Vorab-Einrede). Show/hide driven by selectedType in
- the client. */}
-
-
-
- Mit Widerklage auf Nichtigkeit
-
-
-
-
-
- Mit Antrag auf Patentänderung (R.30)
-
-
-
-
-
- Mit Antrag auf Patentänderung (R.49.2.a)
-
-
-
-
-
- Mit Verletzungswiderklage (R.49.2.b)
-
-
-
-
-
-
-
-
- 3
- Ergebnis
-
-
- {/* m/paliad#149 Phase 2 P3 — three-way detail filter.
- Controls how much of the procedural shape renders:
- just mandatory; mandatory + selected (default); or
- every option including unselected ones rendered
- muted. State persists in localStorage under
- verfahrensablauf:view_mode. The toggle drives a
- client-side filter pre-render; the calc payload
- stays the same, so flipping is instant. */}
-
- Anzeige:
-
-
- Nur Pflicht
-
-
-
- Gewählt
-
-
-
- Alle Optionen
-
-
-
-
- Ansicht:
-
-
- Spalten
-
-
-
- Zeitstrahl
-
-
-
- Hinweise anzeigen
-
- {/* Durations toggle (m/paliad#133, t-paliad-302).
- Default off — hover-tooltips on date spans are
- the always-on path. */}
-
-
- Dauern anzeigen
-
-