All 12 questions answered via AskUserQuestion. 10/12 = inventor recommendation.
2 diverged:
Q3 (Filter-vs-qualifier UX): m picked section-split (Filter strip above,
result/qualifier strip below) instead of '(Pflichtangabe)' tag.
§3.1 Mode A layout rewritten with Filter strip header; §3.2 wizard
rows now carry Filter/Qualifier badges next to the row number.
Q7 (No-project mode): m picked 'Hide CTA entirely' instead of disabled-
with-hint. §4.4 footer renders only when project != null; an inline
'Tipp: Wähle oben eine Akte' nudge replaces the missing footer.
New §11 'm's decisions (2026-05-26)' anchors each pick with reasoning where
it diverges from the recommendation. §11.1 captures the two follow-on edits
to §3.1 and §4.4. Migration plan and backend contracts unchanged.
DESIGN READY FOR REVIEW pending head's coder gate.
44 KiB
Design — Fristenrechner complete UX overhaul (dual-mode + project write-back)
Task: t-paliad-322 Gitea: m/paliad#146 Inventor: cronus (shift-1) Date: 2026-05-26 Status: Draft for m's ratification — coder gate held
0. Premises verified live (before designing)
Verified against the live youpc Postgres (port 11833, paliad schema) and the live source tree on mai/cronus/inventor-fristenrechner @ HEAD.
0.1 Rule-and-event corpus today
| Table | Active+published rows | Notes |
|---|---|---|
paliad.procedural_events |
222 (236 total) | The events that anchor a deadline. 4 event_kind buckets: filing, hearing, decision, order, plus NULL for legacy/dpma stragglers. |
paliad.sequencing_rules |
231 | The deadlines themselves, anchored on procedural_event_id and (sometimes) trigger_event_id. 80 carry a trigger_event_id, 4 are is_spawn=true, 45 are is_court_set=true, 18 carry a condition_expr. |
paliad.deadline_concepts |
57 | Hub layer above events (Klageerhebung, Wiedereinsetzung, …). |
paliad.proceeding_types |
46 fristenrechner | 4 jurisdictions: UPC (35), DE (5), EPA (3), DPMA (3). |
paliad.event_categories |
125 (103 leaves) | The current cascade tree — 5 user-bucket roots (cms-eingang, muendl-verhandlung, beschluss-entscheidung, frist-verpasst, ich-moechte-einreichen) + sonstiges leaf. UI hides the forward-workflow root (HIDDEN_CASCADE_ROOTS in client/fristenrechner.ts:2605). |
paliad.deadlines |
10 (8 with sequencing_rule_id) |
Demand-side still tiny. The 2 without sequencing_rule_id are manual entries. |
Live primary_party vocabulary on sequencing_rules: claimant, defendant, both, court, NULL. Live priority vocabulary: mandatory, recommended, optional (no informational rows yet — Phase 2 reserved the slot but seeding is deferred).
0.2 The legacy deadline_rules reader is a view
paliad.deadline_rules_unified (mig 139, Slice B.3) is a view over sequencing_rules ⋈ procedural_events ⋈ legal_sources. All Go calculator paths read through it (see deadline_rule_service.go:70). The physical paliad.deadline_rules table was dropped in mig 140; the view is the canonical legacy-shape reader. Important for this design: there is no "trigger event" table parallel to events — the rule rows themselves are the things the wizard must land on. trigger_events (110 rows) is the youpc-parity legacy table used by /api/tools/event-deadlines only.
0.3 The frontend today (/tools/fristenrechner)
Two server-rendered surfaces share the same page (frontend/src/fristenrechner.tsx, 657 lines) — the legacy "Procedure mode" (R1 step-list, proceeding picker, trigger date, flag checkboxes) and the Pathway-B row stack (buildRowStack at client/fristenrechner.ts:2848, 4009 lines total). Row stack composes three row kinds via a single .fristen-row primitive:
| Row | Source | Filter or qualifier today |
|---|---|---|
| R1 Perspective (Beide / Klägerseite / Beklagtenseite) | currentPerspective, prefilled from project.our_side |
hybrid — narrows party-tagged cascade chips AND is used as a column-bucket hint in the result view |
| R2 Inbox channel (CMS / beA / Postal / Alle) | currentInboxChannel |
filter — narrows cascade by forum (CMS → upc, beA → de, …) |
| R3..Rn Cascade chain | event_categories tree |
each step narrows children by inboxFilterAllowsForums + perspectiveAllowsParty + cascadeChildAllowsProject |
The cascade auto-walks single-child branches under a project context and stops at the first branching point. The user picks a leaf; the leaf's slug feeds /api/tools/fristenrechner/search?event_category_slug=… which returns concept cards. Each card expands inline to a calc panel (trigger-date input + flags + computed deadline + "in Akte" CTA).
0.4 What is broken in this UI (m's verdict, 2026-05-26 21:21)
m's brief in m/paliad#146 enumerates four visible bugs:
- "Beide" as default perspective is incoherent for the headline use case ("file a deadline because something happened" — you ARE one side).
- R2 (inbox) does not constrain R3 (cascade) the way a user expects — picking CMS still leaves "Mündliche Verhandlung" / "Frist verpasst" on the next step. (Cause: those roots have
forums=NULLin the seed → neutral → visible from every inbox.) - Mixed axes — the form layers filters (forum, inbox channel) on top of qualifiers (event-kind, perspective, proceeding_type) without making the difference visible. The user can't tell which picks narrow and which define.
- Trigger vs follow-up confusion — the wizard's purpose is to identify the trigger event, then surface the follow-up deadlines. Today that split is not reflected in the form. After landing on a leaf, the user gets a flat list of concept cards and has to figure out which one is "the thing that happened" vs "the thing I have to file next".
m's verdict: "complete overhaul. Should be easy to use."
0.5 Anchor files for the eventual coder
frontend/src/client/fristenrechner.ts(4009 LoC) — page brain.buildRowStack@ L2848,renderRowStack@ L3112,runB1Search(concept-card render) downstream,expandCardCalc@ L1337 (inline calc panel),openSaveModal@ L290 +submitSave@ L374 (project write-back).frontend/src/fristenrechner.tsx(657 LoC) — server-rendered shell. Contains both the Procedure-mode form and the Pathway-B row-stack scaffold. The new design replaces the row-stack scaffold; the procedure-mode form survives.internal/handlers/fristenrechner.go+_search.go+_event_categories.go— three handler files.POST /api/tools/fristenrechner(procedure-mode calc),GET /search(concept cards),GET /event-categories(cascade tree).internal/services/fristenrechner.go(661 LoC) — calculator adapter topkg/litigationplanner. The calculator is not touched by this design.internal/handlers/deadlines.go:167+services/deadline_service.go:411(CreateBulk) — the project write-back endpoint (POST /api/projects/{id}/deadlines/bulk). This survives; the design extends its caller.
0.6 Adjacent design docs to read alongside
docs/design-determinator-row-cascade-2026-05-13.md— the row-cascade pillars (Project-driven narrowing / Visual hierarchy overhaul / Persistent row stack). This overhaul keeps Pillars 2 and 3 and reworks Pillar 1's contract.docs/design-fristen-phase2-2026-05-15.md— the unifiedsequencing_rulesmodel the calculator already runs on.docs/audit-fristen-logic-2026-05-13.md— the trigger-event / Pipeline-A-vs-C distinction.
1. Vision
One page, two complementary entry paths, one result surface, one write-back.
┌───────────────────────── /tools/fristenrechner ─────────────────────────┐
│ │
│ ╭──────── Akte / kontextfrei ────────╮ (Step 0 — unchanged today) │
│ │ HL-2024-001 ▼ | ohne Akte │ │
│ ╰─────────────────────────────────────╯ │
│ │
│ ╭────── Entry mode tabs ──────╮ │
│ │ [⚡ Direkt suchen] │ ◀── A: power user, search + chips │
│ │ [🧭 Geführt] │ ◀── B: 3-5 question wizard │
│ ╰─────────────────────────────╯ │
│ │
│ ┌── Mode A: Suche ──────────────┐ ┌── Mode B: Wizard ────────────────┐│
│ │ search-box ▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢ │ │ R1 Was ist passiert? ✓ filing ││
│ │ filter chips: │ │ R2 Forum? ✓ UPC ││
│ │ Forum · Proceeding · Event- │ │ R3 Verfahren? ✓ INF ││
│ │ Kind · Partei │ │ R4 Welcher Schritt? [active] ││
│ │ ┌ Ergebnis-Liste ────────────┐│ │ R5 Welche Seite? ✓ Kläger ││
│ │ │ procedural_event hits ││ │ ││
│ │ │ [Trigger einrasten →] ││ │ (Direkt-suchen ←) ││
│ │ └────────────────────────────┘│ └───────────────────────────────────┘│
│ └────────────────────────────────┘ │
│ │
│ ════ shared from here ═══════════════════════════════════════════════ │
│ │
│ ┌── Trigger event (locked) ──────────────────────────────────────────┐ │
│ │ 📥 Klageschrift wurde eingereicht │ │
│ │ upc.inf.cfi · Verletzungsverfahren · Klägerseite │ │
│ │ Trigger-Datum: [📅 2026-05-20] (heute) │ │
│ │ ändern ↩ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── Folge-Fristen ────────────────────────────────────────────────────┐ │
│ │ ◉ MANDATORY (auto-checked) │ │
│ │ ☑ Klageerwiderung (3 Monate) — 20.08.2026 — RoP 23 ✏ Datum │ │
│ │ ☑ ... │ │
│ │ ◇ OPTIONAL │ │
│ │ □ Wiedereinsetzungsantrag (R.320) — bei Versäumnis │ │
│ │ ◊ CONDITIONAL │ │
│ │ □ Erwiderung auf Nichtigkeitswiderklage nur wenn CCR │ │
│ │ ⇲ SPAWNED │ │
│ │ ☑ Berufung gegen Endurteil (kein Datum) │ │
│ │ ╭────────────────────────────╮ │ │
│ │ │ 4 ausgewählt → in Akte ▶ │ │ │
│ │ ╰────────────────────────────╯ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
The two modes never compete: they're two front doors into the same locked-trigger-event → follow-up-list → write-back flow.
2. Axis taxonomy — ratified (filters vs qualifiers)
The headline source of today's UX confusion is the unmarked mixing of filters (narrowing the question space without committing to an answer) and qualifiers (parts of the eventual deadline definition).
| Axis | Role | Source | Constrains | Visual in new UI |
|---|---|---|---|---|
forum |
filter | derived from proceeding_types.jurisdiction (UPC/DE/EPA/DPMA), or from project.proceeding_type_id, or user pick |
which proceeding_types are reachable; which event_categories are visible |
Mode A: filter chip strip. Mode B: explicit wizard row (R2). Pre-filled + collapsed when there's a project. |
proceeding_type |
qualifier | project.proceeding_type_id (binds via mig 096 codes) OR user pick during wizard |
the set of sequencing_rules rows that can apply |
Mode A: filter chip strip. Mode B: explicit wizard row (R3). Pre-filled + collapsed when there's a project. |
event_kind |
filter | procedural_events.event_kind (filing / hearing / decision / order) |
which procedural_events are reachable as triggers |
Mode A: filter chip strip. Mode B: explicit wizard row (R1 — the headline question). |
inbox channel |
filter (today) → out of scope row (new) | user pick | nothing the user can see (the rule corpus has no "inbox" column; it was only used to recolour the cascade) | Removed from the primary wizard. Pushed into Mode A's secondary chips (off by default). See §3.3. |
perspective (our_side) |
qualifier in file-mode, filter in explore-mode | project.our_side OR user pick OR implicit-via-event-kind |
sequencing_rules.primary_party; result-view column bucketing |
Wizard tail (R5) only when the trigger event's follow-ups actually differ by side. Pre-filled when project has our_side. |
instance_level (first / appeal / cassation) |
qualifier | project.instance_level (mig 084) — sparse |
rare — used to disambiguate APP+DE | Surfaced only when the wizard hits APP+DE-style ambiguity. |
Rule: a filter narrows the visible options without locking in a deadline answer; it can be cleared and re-applied. A qualifier is part of the resulting deadline calculation and is locked the moment it's picked. Filters must propagate forward (Mode A's forum-chip narrows the proceeding-chip's options). Qualifiers are picked once and the answer view reads them.
The "Beide" perspective default (today's bug) is wrong because perspective is a qualifier in the headline use case ("file a deadline because something happened — you are one side"), not a filter. New default in Mode B: derive from the project's our_side, otherwise force a R5 pick (no "Beide"). See Q8 for the explore-mode exception.
3. Mode taxonomy
3.1 Mode A — "⚡ Direkt suchen" (power user)
Two visually distinct strips (per m §11.Q3):
┌── Filter (eingrenzen) ─────────────────────────────────────────────────┐
│ Forum: [UPC] [DE] [EPA] [DPMA] [Alle] │
│ Verfahren: [upc.inf.cfi] [...] [Alle] │
│ Was passierte: [📥 Eingereicht] [🏛️ Termin] [⚖️ Entscheidung] [📜 Verfügung] [Alle] │
│ Partei: [Klägerseite] [Beklagtenseite] [Beide] │
├── Suchen ──────────────────────────────────────────────────────────────┤
│ 🔎 [_______________________________________________________________] │
└─────────────────────────────────────────────────────────────────────────┘
┌── Ergebnisse (klicken = als Trigger einrasten) ────────────────────────┐
│ 📥 Klageerhebung · upc.inf.cfi · UPC · 3 Folge-Fristen → │
│ ... │
└─────────────────────────────────────────────────────────────────────────┘
Single text input, four filter chip strips above it (Forum · Proceeding · Event-Kind · Partei), and a ranked result list of procedural_events underneath. The "Filter" strip is visibly grouped (e.g. light background + "Filter (eingrenzen)" header) so users see at a glance that those picks narrow but don't commit; clicking a result row IS the commit (the qualifier action).
- Search hits
/api/tools/fristenrechner/search(extended to return events, not just concepts — see §6.1). - Filter chips compose with the text query (
?forum=upc&pt=upc.inf.cfi&kind=filing&party=defendant&q=Klageerwiderung). - Result rows are individual
procedural_events(not aggregated concept-cards). Each row shows: name (DE/EN), proceeding_type code, jurisdiction badge, event_kind icon, the rule-count it triggers ("3 Folge-Fristen"). - Click a row → "lock as trigger event" → page transitions to the §4 result view.
- Power affordance: a row with multiple linked rules can be locked in per-rule ("nur diese Frist") via a kebab menu on the row. (Sane default: lock the whole event; the kebab is for the lawyer who only wants one specific reactive deadline.)
3.2 Mode B — "🧭 Geführt" (the wizard)
A 3-5 question row stack that lands on one procedural_events row.
Question order (strawman; m to ratify in Q5):
- R1 — Was ist passiert? Chips: 📥 Eingereicht (
filing) · 🏛️ Termin (hearing) · ⚖️ Entscheidung (decision) · 📜 Verfügung (order) · 🕒 Frist versäumt (special bucket, routes to Wiedereinsetzung). One chip = oneevent_kind(or the special). Always asked. ~6 chips, fits one row. - R2 — Vor welchem Gericht / bei welchem Amt? Chips: UPC · LG/OLG/BGH · EPA · DPMA. Pre-filled from
project.proceeding_type → jurisdiction(orproject.courtsubstring). Skipped if R1 narrows to a single forum (e.g. "Termin" + project has UPC → R2 is implied). - R3 — In welchem Verfahren? Chips: every active
proceeding_typewhose jurisdiction matches R2 AND whose event roster contains at least one event with R1's kind. Pre-filled fromproject.proceeding_type_id. Auto-skipped when the narrowed scope has only one candidate. - R4 — Welches Schriftstück / Welcher Termin? This is the wizard's landing question. Chips =
procedural_eventsfiltered by (R2 forum, R3 proceeding_type, R1 event_kind). Typical scope: 1-12 events. If the user types into this row, the chip layout flips to a search list (same widget as Mode A's result list, narrowed to the wizard's filters). - R5 — Vertreten Sie Kläger- oder Beklagtenseite? Asked only when the selected event's
sequencing_ruleshave follow-ups that differ byprimary_party(a quick "are there both claimant- and defendant-tagged rules among the follow-ups?" check on the catalog). Pre-filled fromproject.our_side. Skipped otherwise.
Row badges (per m §11.Q3): each wizard row carries a small "Filter" or "Qualifier" tag next to its row-number badge. R1 (event_kind), R2 (forum) → "Filter". R3 (proceeding_type), R4 (procedural_event), R5 (perspective) → "Qualifier". A user can tell at a glance which picks lock in vs which narrow.
Branching policy (locked):
- Pre-fill + collapse a row when the answer is implied by the project (Determinator §4 pattern, unchanged).
- Auto-skip a row when the narrowed scope has exactly one option (the user has effectively no choice). Show the skipped row as a compact
.fristen-row.is-prefilledline with "(aus Akte)" or "(implizit aus R1)" annotation. Don't hide the row — m's "see your selections" pillar from the row-cascade design demands every decision stays visible. - A user-edited upstream answer preserves compatible downstream picks (m §11.Q10): if a re-picked R2 (forum) keeps the existing R1 (event_kind) legal, R1 stays; if it makes R3 (proceeding_type) illegal, R3 resets to active. Rows whose pick was carried across an upstream change render with a one-render "erhalten" annotation so the user notices.
- "Welches Schriftstück?" (R4) is the landing question. Once R4 is answered, the wizard exits and the §4 result view takes over.
3.3 The dropped inbox channel row
R2-inbox in today's row stack is removed from the primary surface for both modes. Rationale:
- The rule corpus has no
inboxcolumn. The cascade'sforums=['cms']etc. tags were a presentation-layer reflection of which forum naturally arrives on which channel — but the rule itself doesn't change based on whether a UPC document arrived via CMS or by post (it can't; only CMS is legal). So the only honest role for "inbox" is to nudge the forum filter on Mode A. - Mode A keeps inbox as a secondary chip strip ("Erweitert" toggle, off by default). Picking CMS auto-sets the forum chip to UPC; picking beA auto-sets it to DE. The user can override.
- Mode B never asks. The wizard derives forum from project context or from R2.
This collapses one bug class entirely (R2-not-constraining-R3) by retiring R2 from the headline path.
4. Shared result view — "follow-up deadlines"
Once a trigger event is locked (via Mode A click or Mode B R4 pick), the same result view renders.
4.1 Trigger card (sticky header)
┌─ Trigger-Ereignis ─────────────────────────────────────────────────────┐
│ 📥 Klageerhebung │
│ upc.inf.cfi · Verletzungsverfahren · UPC │
│ ⓘ "Einreichung der Klageschrift gemäß R.13 RoP" │
│ Trigger-Datum: 📅 2026-05-20 [ändern ↩] │
└─────────────────────────────────────────────────────────────────────────┘
Trigger-Datum defaults to today. The user can change it inline (date picker). Changing it re-renders the follow-ups with new computed dates.
The "ändern" link drops back to whichever mode the user came from with R1-R4 still answered. (Per Q4: the wizard preserves compatible upstream picks rather than rebooting.)
4.2 Follow-up groups
Group sequencing_rules rows that have the trigger event as anchor (i.e. sr.procedural_event_id = trigger.id) into 4 visible groups:
- MANDATORY (
priority='mandatory') — pre-checked. The bread-and-butter follow-ups. - RECOMMENDED (
priority='recommended') — pre-checked. Best-practice fillings (R.19 EPÜ Einspruch, replication briefs). - OPTIONAL (
priority='optional') — unchecked. Discretionary actions (R.320 Wiedereinsetzung). - CONDITIONAL (
condition_expr IS NOT NULL) — unchecked, with the condition rendered ("nur wenn CCR im Verfahren"). Lawyer ticks if applicable.
Plus a fifth implicit bucket:
- SPAWNED / CROSS-PROCEEDING (
is_spawn=true,spawn_proceeding_type_id IS NOT NULL) — surfaced as a separate sub-section with a clear "leitet ein neues Verfahren ein" annotation. Pre-checked when mandatory.
Recommendation (Q6): 4 visible groups, with SPAWNED inlined into whichever priority bucket it belongs to but tagged with a "⇲ neues Verfahren" badge. Five groups is too many for a one-page result; folding SPAWNED into its priority keeps the math right (mandatory spawned = mandatory) while still flagging the cross-proceeding implication.
4.3 Per-rule row
☑ Klageerwiderung ✏ Datum
3 Monate nach Klageerhebung 20.08.2026
RoP 23 · Beklagtenseite
ⓘ Schriftlich, mit Vollmacht. Erstmaliges Bestreiten der Patentverletzung.
Columns: checkbox · title (DE/EN) · duration phrase · computed due date · rule citation · party stance · expandable notes.
Inline date editor (✏ Datum) lets the lawyer override the computed date for this rule (same affordance as today's wireDateEditClicks). The override flows into the write-back payload.
is_court_set=true rules render with the "wird vom Gericht bestimmt" placeholder instead of a date and are unchecked-by-default (matches the current openSaveModal behaviour).
4.4 Result-view footer (write-back CTA)
┌─ Auswahl ──────────────────────────────────────────────────────────────┐
│ 4 Fristen ausgewählt → In Akte HL-2024-001 eintragen ▶ │
│ (oder: 2 mit eigenem Datum, 2 mit Standardberechnung) │
└─────────────────────────────────────────────────────────────────────────┘
The CTA opens a confirm-and-edit-dates modal (per m §11.Q6) where the lawyer can revise each selected deadline's due date one last time, then commits via POST /api/projects/{id}/deadlines/bulk (today's endpoint).
Kontextfrei mode (no Akte) — per m §11.Q7, the entire write-back footer does not render when project == null. The result view stays informational. In its place, an inline nudge appears above the deadline groups:
ⓘ Tipp: Wähle oben eine Akte, um diese Fristen einzutragen.
The "oben" link focuses the Akte picker. Once a project is picked, the nudge collapses and the footer materialises; no page reload, no result-view rebuild (the trigger event and date persist across the project pick).
Modal payload per deadline (extends today's CreateDeadlineInput):
{
"title": "Klageerwiderung",
"rule_code": "RoP 23",
"due_date": "2026-08-20",
"original_due_date": "2026-08-20",
"source": "fristenrechner",
"rule_id": "<sequencing_rules.id>", /* maps to deadlines.sequencing_rule_id */
"notes": "..."
}
audit_reason wording (per Q12): every row inserted via this flow carries an audit-log breadcrumb on the project (matches the deadline Verlauf pattern). Default reason string:
Aus Fristenrechner — Trigger: {trigger_event_name} ({trigger_date_iso})
e.g. Aus Fristenrechner — Trigger: Klageerhebung (2026-05-20). Falls into paliad.project_events with kind='deadline_created' via the existing DeadlineService.CreateBulk audit hook; no schema change needed.
5. URL / state representation
The new flow keeps Pathway-B's URL-as-state contract, simplified:
| Param | Owner | Meaning |
|---|---|---|
project |
Step 0 | Active project UUID. Drives the prefills. |
mode |
mode tab | wizard (default) or search. |
q |
Mode A | Free text query. |
forum |
Mode A | Comma-separated forum codes (upc,de). Mode B writes this only when the wizard derives it. |
pt |
Mode A | Selected proceeding_type code. |
kind |
Mode A | event_kind chip pick. |
party |
both | Perspective. Mode A's chip; Mode B's R5. |
wizard |
Mode B | Dotted state cursor encoding which row is active and the picks made: wizard=kind:filing,forum:upc,pt:upc.inf.cfi,active:event. |
event |
both | The locked trigger procedural_events.code. Once set, the result view renders. |
trigger_date |
result | ISO date. Default = today; URL only carries it when overridden. |
selected |
result | Comma-separated sequencing_rules.id checkbox state. Only carried when it differs from the priority default. |
Deep links work end-to-end: ?project=…&event=upc.inf.cfi.soc&trigger_date=2026-05-20&selected=… jumps a colleague straight to the result view with the same picks. (Per Q11 — query string, not pathname.)
popstate rebuilds the page from the params alone (same pattern as today). The wizard state cursor lets browser back/forward step the wizard rows instead of dropping back to the page root.
6. Backend contract changes
6.1 Extend /api/tools/fristenrechner/search
Today returns concept-cards. Add an alternate response shape (or a ?kind=events flag) that returns procedural_events rows directly:
{
"query": "Klageerhebung",
"filters": { "forum": "upc", "pt": null, "kind": "filing", "party": null },
"events": [
{
"id": "<uuid>",
"code": "upc.inf.cfi.soc",
"name_de": "Klageerhebung",
"name_en": "Statement of Claim",
"event_kind": "filing",
"proceeding_type": { "code": "upc.inf.cfi", "jurisdiction": "UPC", "name": "..." },
"follow_up_count": 3,
"concept_id": "<uuid>",
"score": 0.92
}
],
"total": 12
}
The concept-card shape stays available for the legacy Pathway-B-filter route (kept as a deep-link compat surface, not user-facing).
6.2 New /api/tools/fristenrechner/follow-ups
Given a trigger event id + trigger date + optional party qualifier, return the follow-up sequencing_rules rows, grouped + with computed dates. Wire shape:
{
"trigger": { "id": "...", "code": "upc.inf.cfi.soc", "name_de": "Klageerhebung", "event_kind": "filing", "proceeding_type": { "code": "upc.inf.cfi", "name_de": "Verletzungsverfahren", "jurisdiction": "UPC" } },
"trigger_date": "2026-05-20",
"party": "claimant",
"follow_ups": [
{
"rule_id": "<uuid>",
"title_de": "Klageerwiderung",
"title_en": "Defence",
"priority": "mandatory",
"primary_party": "defendant",
"duration_phrase": "3 Monate",
"due_date": "2026-08-20",
"is_court_set": false,
"is_spawn": false,
"condition_expr": null,
"rule_code": "RoP 23",
"notes_de": "...",
"spawn_label": null,
"spawn_proceeding_type": null,
"appeal_target": null
}
]
}
Implementation: FristenrechnerService.LookupFollowUps(ctx, eventID, triggerDate, party) — wraps catalog.LookupEvents(axes={EventID:…, Depth:Next}) (already implemented for the Litigation Planner per services/fristenrechner.go:251) and runs the result through pkg/litigationplanner.Calculate to fill the dates. The calculator is unchanged.
6.3 No schema changes
This design is pure UX + handler shape. The unified sequencing_rules model already has every column needed (priority, condition_expr, spawn_*, is_court_set, primary_party, applies_to_target). No migration accompanies this design.
7. Migration plan — from current row stack to the overhaul
Drop nothing on day one; co-exist for one release. The cutover is by URL flag.
| Phase | What changes | What survives | Branch |
|---|---|---|---|
| S1 — Backend | Add GET /search?kind=events. Add GET /follow-ups. Both feature-flagged behind a request header (off by default). |
Existing endpoints. | one PR |
| S2 — Result view | New frontend/src/client/fristenrechner-result.ts module — given a trigger event + date, render the §4 result view. Mount under a ?overhaul=1 query flag on /tools/fristenrechner. The legacy renderProcedureResults stays. |
All today's UI. | one PR |
| S3 — Mode A | New search-with-filter-chips UI. Mount alongside the row stack under ?overhaul=1. |
Row stack still primary. | one PR |
| S4 — Mode B (wizard) | New frontend/src/client/fristenrechner-wizard.ts — the 3-5 row stack. Replaces today's buildRowStack only when ?overhaul=1. Project prefill logic from buildRowStack ports 1:1. |
The legacy row stack stays in place under no flag. | one PR |
| S5 — Flip the flag | ?overhaul=1 becomes the default. Legacy row stack and event_categories-based cascade rendered with a hard-coded ?legacy=1 for two weeks. |
Procedure mode (the upper half of fristenrechner.tsx) is unchanged throughout. |
one PR |
| S6 — Cleanup | Drop the buildRowStack function tree and the event_categories-served cascade endpoint (the table can stay — it's still semantically a useful taxonomy for future tools, just not the Fristenrechner's UI). Drop the HIDDEN_CASCADE_ROOTS constant and the cascade-segment bridge. |
None of today's row-stack code. | one PR |
Single project per slice; each PR rebases off main; no shared branches.
The event_categories table itself stays — audit-fristen-logic-2026-05-13.md §2.4 already calls it "a config layer" useful for taxonomy work. The Fristenrechner just no longer reads it. Future tools (the "Ich möchte einreichen" forward-workflow surface m hid in HIDDEN_CASCADE_ROOTS) can resurrect it without DB migration.
8. Worked example — "PA at LG Düsseldorf bekommt einen Hinweisbeschluss via CMS in einer aktiven Akte"
Project: HL-2024-001, proceeding_type=de.inf.lg (Verletzungsverfahren LG), our_side='defendant', court='LG Düsseldorf'.
8.1 Wizard path (Mode B, default)
User opens /tools/fristenrechner with that project in Step 0. Mode tab defaults to "🧭 Geführt".
Wizard rows render top-to-bottom, pre-filled where the project implies:
[1] Was ist passiert? [ active — chips for filing/hearing/decision/order/missed ]
[2] Vor welchem Gericht? ✓ LG (aus Akte: HL-2024-001) ← prefilled+collapsed
[3] In welchem Verfahren? ✓ Verletzungsverfahren (de.inf.lg) ← prefilled+collapsed
User clicks ⚖️ Entscheidung in R1.
Row stack updates:
[1] Was ist passiert? ✓ Entscheidung ← answered
[2] Vor welchem Gericht? ✓ LG (aus Akte) ← prefilled
[3] In welchem Verfahren? ✓ Verletzungsverfahren (de.inf.lg) ← prefilled
[4] Welche Entscheidung konkret? [ active — chips: Urteil, Beschluss, Hinweisbeschluss, ... ]
R4 chip set is the procedural_events whose proceeding_type_id = de.inf.lg AND event_kind = 'decision'. (Hinweisbeschluss is in this set — de.inf.lg.hinweisbeschluss or similar.)
User clicks Hinweisbeschluss. The wizard checks: do the follow-up rules differ by primary_party? In this case yes (the Hinweis triggers a reply window for the defendant only). So R5 fires:
[5] Welche Seite vertreten Sie? ✓ Beklagtenseite (aus Akte) ← prefilled
R5 is pre-filled from project.our_side='defendant'. The user could click ändern to override, but doesn't.
Wizard transitions to the §4 result view. Trigger card: "📜 Hinweisbeschluss · de.inf.lg · LG · Beklagtenseite". Trigger date defaults to today.
8.2 Result view
Three follow-ups in scope (illustrative):
MANDATORY
☑ Stellungnahme zum Hinweisbeschluss (Frist 4 Wochen) — 24.06.2026 — ZPO §139
RECOMMENDED
☑ Anpassung der Klageerwiderung — 24.06.2026 — best practice
OPTIONAL
□ Antrag auf Fristverlängerung (begründet) — auf Antrag
User unchecks "Anpassung", changes the Stellungnahme date inline to 2026-06-20 (one weekday earlier), clicks "In Akte HL-2024-001 eintragen ▶".
Modal opens with the 1 selected deadline + the user's date override. User confirms.
8.3 Write-back
Server-side: POST /api/projects/HL-2024-001/deadlines/bulk with one CreateDeadlineInput:
{
"title": "Stellungnahme zum Hinweisbeschluss",
"rule_code": "ZPO §139",
"due_date": "2026-06-20",
"original_due_date": "2026-06-24",
"source": "fristenrechner",
"rule_id": "<sr-uuid>",
"notes": null
}
DeadlineService.CreateBulk inserts the row into paliad.deadlines (with sequencing_rule_id populated from rule_id), creates the audit event with the wording "Aus Fristenrechner — Trigger: Hinweisbeschluss (2026-05-26)", and the user is redirected to /deadlines?project_id=… with a green success toast.
8.4 Mode A path for the same user
User flips the mode tab to "⚡ Direkt suchen". Filter chips auto-load to Forum=DE + Proceeding=de.inf.lg (from project context). User types "Hinweis" — the result list shows de.inf.lg.hinweisbeschluss (and maybe upc.inf.cfi.hinweis filtered out because Forum=DE narrows it). User clicks. Same result view appears.
Total clicks Mode A: 2 (type + click). Mode B: 2 (R1 chip + R4 chip; R2/R3/R5 prefilled). The wizard wins for trainees who don't know vocabulary; search wins for power users who know "Hinweisbeschluss" and can type 4 chars.
9. What's NOT in scope
- Replacing the
sequencing_rulesmodel. Phase 3 schema is already what the calculator runs on. - Paliadin (LLM) integration into the wizard. A "Frist-Extraktion aus Dokument" path is filed elsewhere (memory
b6a11b55…) and stays out of this design. The wizard could later call out to Paliadin for "the user typed something we don't know" — Phase 2 of this overhaul, not Phase 1. - Calendar / Outlook sync of created deadlines. Separate t-paliad ticket per project-status.md long-term goals.
- Editing
sequencing_rulesfrom the result view. Read-only here. The admin surface at/admin/procedural-eventshandles editing. - The Procedure-mode surface (upper half of
fristenrechner.tsx). The proceeding-picker + trigger-date + flag-checkbox UI stays exactly as it is today. That surface answers a different question ("show me the full procedural ablauf for upc.inf.cfi") and is the right tool for that question; the overhaul targets only the Pathway-B / row-stack half of the page.
10. Open questions for m (12 questions, batched for AskUserQuestion)
All 12 questions tracked in m/paliad#146 § "Open design questions". Each gets a recommended option (listed first in the AskUserQuestion call). Bundled into 3 batches of 4.
| # | Topic | Recommended pick |
|---|---|---|
| Q1 | Single page or stepper? | Single page with mode-tabs + collapsible rows. |
| Q2 | Mode switcher placement | Tab pair under Step-0 ("Akte / kontextfrei"). |
| Q3 | Filter-vs-qualifier UX | Qualifiers carry a small "(Pflichtangabe)" tag; filters render in a slimmer pill. |
| Q4 | Cascade tree (keep/replace) | Replace with the 5-question wizard. Drop event_categories from the Fristenrechner UI (table stays). |
| Q5 | Result grouping | 4 visible groups (Mandatory / Recommended / Optional / Conditional), SPAWNED folded with badge. |
| Q6 | Project write-back UX | Confirm-and-edit-dates modal (revise each date once before commit). |
| Q7 | No-project mode | CTA disabled with hint ("Wähle eine Akte oben"). Match today's pattern. |
| Q8 | Perspective semantics by mode | Mode B (file): qualifier — required pick. Mode A (search): filter — optional. |
| Q9 | Trigger-date input timing | In the result-view trigger card; default today; inline editable. |
| Q10 | Backward navigation | Preserve compatible downstream picks; reset only those invalidated. |
| Q11 | Deep-link encoding | Query string (?event=…&trigger_date=…). |
| Q12 | Audit reason wording | Aus Fristenrechner — Trigger: {name} ({date}). |
(Recommendations land as the "first option" in each AskUserQuestion call per the inventor SKILL contract.)
11. m's decisions (2026-05-26)
All 12 questions answered via AskUserQuestion on 2026-05-26 21:30. Recording each pick + the reasoning where it diverges from the inventor's recommendation. Sections of the design that are now load-bearing on these answers carry a "(m §11.Q{n})" cross-reference.
- Q1 (Page layout): Single page, mode-tabs. [= recommendation] Both modes share /tools/fristenrechner; the mode-tabs swap the question surface in place. Result view is shared. Locks §3, §4, §5.
- Q2 (Mode switcher): Tab pair under Step-0. [= recommendation] "⚡ Direkt suchen" / "🧭 Geführt" tabs render directly below the Akte picker. Project context survives the tab flip; compatible filter picks (forum, proceeding) carry across.
- Q3 (Filter-vs-qualifier UX): Section split — Filter above, Qualifier below. [≠ recommendation; m picked option 2.] Mode A's filter chips render in a "Filter (eingrenzen)" strip on top; below it, the result list is the qualifier surface (clicking a row locks). Mode B wizard rows carry a small "Filter" / "Qualifier" badge in the row badge area (e.g. R1/R2 = Filter, R3/R5 = Qualifier). The "(Pflichtangabe)" tag from the original recommendation is replaced by this section-level visual hierarchy. Updates §3.1 (Mode A layout) and §3.2 (wizard row badges).
- Q4 (Cascade tree): Replace with wizard, keep table. [= recommendation] The Fristenrechner UI stops reading
paliad.event_categories. The table stays for future tools (the hidden "Ich möchte einreichen" forward-workflow). Locks §3.2 and the cleanup in §7 S6. - Q5 (Result grouping): 4 groups + SPAWNED badge. [= recommendation] Mandatory / Recommended / Optional / Conditional are the four sub-sections; spawned rules fold into their priority bucket with a
⇲ neues Verfahrenbadge. Locks §4.2. - Q6 (Write-back UX): Confirm-and-edit-dates modal. [= recommendation] Inline checkbox selection in the result view → "In Akte eintragen ▶" → modal with editable due-date fields per row + Akte picker. Locks §4.4.
- Q7 (No-project mode): Hide the CTA entirely. [≠ recommendation; m picked option 3.] In kontextfrei mode the result view renders without the write-back footer at all — no disabled-with-hint button. Rationale (inferred from m's pick): the result view is informational by design in explore mode, and a permanently-disabled CTA is visual noise. Updates §4.4 — the CTA is conditional on
project != null, not ondisabled. The hint message moves into the Step-0 picker: when a user is in kontextfrei mode and reaches a result view, a one-line nudge appears above the result groups ("Tipp: Wähle oben eine Akte, um diese Fristen einzutragen") with a link to focus the Akte picker. This preserves the affordance discovery without polluting the footer. - Q8 (Perspective semantics): Mode B qualifier, Mode A filter. [= recommendation] Wizard Mode B's R5 is required and Klagerseite/Beklagtenseite only (no "Beide"); Mode A's perspective chip is a filter with a "Beide" option, off by default. Locks §2 axis table and §3.2 R5 description.
- Q9 (Trigger-date input): In the result-view trigger card. [= recommendation] The sticky header card on the result view shows the date; default today; inline editable. Changing it re-renders follow-up dates live. Locks §4.1.
- Q10 (Backward navigation): Preserve compatible picks. [= recommendation] Re-opening any wizard row keeps downstream picks that are still legal under the new upstream value; resets only the picks the new value invalidates. A small chip-strip annotation ("erhalten") appears for one render-cycle on rows whose pick was carried so the user notices. Updates §3.2 branching policy.
- Q11 (Deep-link encoding): Query string. [= recommendation]
?project=…&mode=…&event=…&trigger_date=…&selected=…&forum=…&pt=…&kind=…&party=…— every state piece is a query param.popstaterebuilds the page from params. Locks §5. - Q12 (Audit reason wording):
Aus Fristenrechner — Trigger: {name} ({date}). [= recommendation] German-locale, includes the trigger event name and its ISO date. Stored aspaliad.project_events.metadata->>'audit_reason'via the existingDeadlineService.CreateBulkaudit hook. Locks §4.4.
11.1 What changed from the strawman as a result
Two follow-on edits flow from m's picks:
- §3.1 Mode A layout — top strip is "Filter (eingrenzen)" with the four filter chip groups (Forum · Proceeding · Event-Kind · Partei); the result list directly below carries the implicit "click here to lock" qualifier action. No "(Pflichtangabe)" tag.
- §4.4 Write-back footer — the footer is rendered conditionally on
project != null. The kontextfrei-mode informational nudge moves into the result view body above the deadline groups.
These edits don't change the §7 migration plan or the §6 backend contracts.
12. Synthesis links
- mBrian topic:
topic-fristenrechner(existing) — file this design as a[synthesis]node linkedtriggered_byt-paliad-322 andrelated_tothe row-cascade + Phase 2 designs. - Related memories: row-cascade design
0fbd2c1a-…, Phase 2 designa454dc86-…, audit logicf6c0c3a2-….