Files
paliad/docs/plans/prd-procedures-litigation-planner-2026-05-27.md
mAi c945cbd330 docs(prd): fix 3 schema inaccuracies in litigation-planner PRD
planck flagged via mai report feedback (id 12301) after the B5+B6
verification round caught them:

- §5.4 'INSERT into paliad.project_parties' → real table is paliad.parties
- §5.4 'status=open' → real CHECK constraint allows pending/completed/cancelled/waived
- §7.4 listed verfahrensablauf-detail-mode.ts as dead code, but builder
  imports filterByDetailMode from it; struck through with KEEP note.

Code shipped (B5+B6) used the correct values throughout; this aligns
the historical PRD with reality so a future reader doesn't repeat the
verification time planck spent.
2026-05-31 15:16:55 +02:00

56 KiB
Raw Blame History

PRD — Procedures: Litigation Builder (m/paliad#153)

Task: t-paliad-339 Gitea: m/paliad#153 Inventor: edison (shift-1, Opus) Date: 2026-05-27 Branch: mai/edison/inventor-prd-columnar Status: Draft — DESIGN READY FOR REVIEW. Coder gate held.

Builds on (read before extending this PRD):

  • docs/design-procedures-workflow-tracker-2026-05-27.md — atlas's reverted tracker design (m/paliad#152). The anchor+scope idea did not land; understand why before re-proposing.
  • docs/design-unified-procedural-events-tool-2026-05-27.md — cronus's U0-U4 catalog, currently live on main @ ed3c5d1 post-revert. Visual baseline for filter strip + tab control.
  • docs/design-deadline-system-revision-2026-05-27.md — atlas Phase 2 model layer (scenario_flags SSoT, view-mode toggle, per-rule selection chips). Model layer is locked; this PRD is purely surface + new persistence tables.
  • docs/design-fristenrechner-overhaul-2026-05-26.md — cronus 2026-05-26 inventor-pass (Mode A + B + result, shipped via t-paliad-322).

Predecessor takeaway (atlas's debrief on #152):

"When the architecture is novel, default to grilling m in prose FIRST. The doc rewrites cost a commit; the bigger cost would have been wasting m's question-batch on the wrong architecture."

Followed here. This PRD captures the architecture m chose through 20 chip-picker decisions across 5 batches, not an inventor-first strawman.


§0 Premises

§0.1 What is /tools/procedures today (live, post-revert)

The current page is cronus's 4-tab catalog (U0-U4, shipped via m/paliad#151):

  • Sticky filter strip (search box + 4 chip rows: Forum / Verfahren / Ereignisart / Partei).
  • 4 solid tabs: Verfahren wählen / Direkt suchen / Geführt / Aus Akte.
  • Default-active tab = "Verfahren wählen" renders VerfahrensablaufBody (the legacy Verfahrensablauf wizard: proceeding picker → perspective + date → 3-step wizard → result in 3-column "Spalten" or single-column "Zeitstrahl").
  • Other 3 tab panels are stubs (search/wizard/akte never wired in U0-U3).

m's blocking feedback (verbatim, 2026-05-27 22:18):

I like to keep our current columnar layout with proactive / court / reactive. And it is good if we can select which side we want to simulate. […] There are basically three main approaches I see to this: Get an overview over proceedings, play around with options, build Scenarios. Another one where something specific happened and we just want to know what deadlines we need to note […]. A third one from a specific proceeding / case file where things take place / have taken place.

And the architecture-shifting follow-ups (2026-05-27 22:35-22:36, mid-grilling):

I would prefer to have an interface where not every constellation is in the URL by the way. That seems limiting. We could just have a litigation builder. Sometimes we build a full scenario with multiple instances etc, sometimes we just want the next step. we should have ways to save these "litigation constellations" where we save which proceedings we have and which state they are in, which submissions were or were not filed. A small Scenario DB could work, dont you think?

These three statements upgraded the brief from "redesign a catalog" to "build a Litigation Builder backed by a Scenario DB". The PRD below is shaped by them.

§0.2 Locked constraints (m's words, brief in #153)

  • Columnar layout: proaktiv | court | reaktiv (perspective-flippable).
  • Three approaches as entry modes: overview/scenarios, event-triggered, case-file driven.
  • Filtering across all dimensions + text search.
  • Optional follow-ups: toggleable, highlightable, with display-count setting.
  • Modular where it actually helps (m: "I don't know — generally does not super apply here." — drop modular as a load-bearing goal).
  • UPC v1, expand later.

§0.3 Live data the builder works against

Verified 2026-05-27 against paliad.sequencing_rules (231 published / 242 total):

  • 110 chained (parent_id not null).
  • 78 trigger-rooted, 4 spawns (cross-PT), 47 court-set, 18 conditional.
  • ~46 proceeding types total (UPC 35 / DE 5 / EPA 3 / DPMA 3). v1 focuses on UPC.
  • paliad.proceeding_types.kind discriminator (atlas's t-paliad-324) filters non-proceeding rows (phases/side_actions/meta) from the picker.
  • paliad.deadlines carries both procedural_event_id and sequencing_rule_id → Akte actuals overlay is a direct join.
  • paliad.projects.scenario_flags jsonb (atlas P0) is the SSoT for project-level scenario state; the new paliad.scenario_proceedings.scenario_flags mirrors this shape per-proceeding-per-scenario.

§0.4 Scope (in / out)

In:

  • Replace /tools/procedures with the Litigation Builder.
  • New paliad.scenarios + paliad.scenario_proceedings + paliad.scenario_events + paliad.scenario_shares tables.
  • Promote-to-project flow (scenario → paliad.projects row).
  • Bidirectional link from /projects/{id} (button: "Im Builder öffnen" — exports project state to a builder session).

Out (deferred or owned elsewhere):

  • Calculator (pkg/litigationplanner.CalculateRule) — working.
  • Editorial backfill (curie's t-paliad-333 owns the 7 compound rules + R.109).
  • /admin/procedural-events (editor surface; different audience).
  • /projects/{id} Verlauf / SmartTimeline (per-Akte actuals; sister tool).
  • youpc.org / Outlook / PDF export.
  • Multi-jurisdiction expansion (DE/EPA/DPMA) — UPC v1 first.
  • Cross-proceeding peer triggers (UPC-inf judgment → EPA opp choice deadline) — v1.1.
  • Multi-user concurrent editing on the same scenario (out of scope; sharing is read-only).

§1 Goals

  1. One canvas, three entry modes. Unify the 3 approaches into a single Litigation Builder surface. The entry modes (Übersicht / Ereignis / Aus Akte) shape the initial state of the canvas; once the user is working, the canvas itself is what they interact with.
  2. Persisted constellations. A user can save a "litigation constellation" — multiple parallel proceedings with their flags, filed/skipped/planned event states, dates, and notes — as a named scenario. Scenarios live in the DB (not the URL).
  3. Auto-save by default. No "unsaved changes" modals. The active scenario auto-persists. Anonymous scratch scenarios convert to named ones when the user clicks "Benennen".
  4. Promote-to-project. A scenario can be turned into a real paliad.projects row via a 3-step wizard. Procedural shape, placeholder parties, notes, and filed-state all carry over; the user fleshes out client-bound metadata during the wizard.
  5. Share read-only with the team. Each scenario is private by default; explicit "An Team teilen" grants named HLC users read-only access. Original owner stays sole editor.
  6. Columnar geometry restored. The current "Spalten" view (claimant | court | defendant) returns as the canonical render — but now per-proceeding-triplet within a scenario, with perspective ("our side") flippable per proceeding so proaktiv | court | reaktiv reads correctly across multi-proceeding constellations.
  7. Per-event-card optional horizon. Each event card on the canvas can dial in how many optional follow-ups to surface. Cards are the unit of optional-display control.

§2 User journeys

§2.1 Journey A — Cold-open builder ("Übersicht / Scenarios")

Persona: Dr. Becker, senior partner. Friday afternoon. New UPC matter not yet committed; she's briefing a client on Monday on the full procedural shape.

  1. Opens /tools/procedures. No ?scenario param. Cold-open canvas: empty workbench with a "Neues Szenario starten" CTA and a short list of her 5 most-recent scenarios.
  2. Clicks the CTA → inline picker (Forum chip row → Verfahren chip row → Hinzufügen). Picks UPC + upc.inf.cfi.
  3. Canvas now renders one proceeding triplet (proaktiv | court | reaktiv). Default perspective is empty (no party selected) — both sides render equally; the perspective radio in the page header sits unset.
  4. She picks defendant perspective at the page header → triplet flips. The defendant column becomes proaktiv (her side); claimant becomes reaktiv.
  5. She adds a second proceeding via + Verfahren hinzufügen at the bottom: EPA epa.opp.opd. New triplet stacks below the first. New triplet's perspective defaults to "patentee" inheriting from her client's role across the two; she flips per-proceeding via the triplet header.
  6. She turns on with_ccr on the UPC inf triplet's per-proceeding flag strip. The CCR child triplet auto-expands inline below the parent at the spawn node.
  7. Auto-save kicks in (debounced 500ms). The page header shows "Gespeichert in Scratch · Benennen".
  8. She clicks "Benennen", enters "Becker — UPC + EPA defensive". Side panel "Meine Szenarien" updates.
  9. On Monday she opens the scenario from her recent list, walks the client through it, hits "Als Projekt anlegen" (when the client commits). 3-step wizard fires (§5.4).

§2.2 Journey B — Event-triggered lookup ("Ereignis")

Persona: Sandra, paralegal. Today: a Hinweisbeschluss arrived on a CMS queue. She doesn't know yet which Akte it belongs to.

  1. Opens /tools/procedures. Picks "Ereignis" entry mode at the top.
  2. Page-header search box auto-focuses. She types "Hinweis" → universal search drops down: 5 Ereignisse · 1 Szenario · 0 Akten. Picks the event upc.inf.cfi.cmo_review (Antrag CMO-Überprüfung).
  3. Canvas renders one triplet of upc.inf.cfi with the Hinweisbeschluss event card auto-anchored (lime band + ━━ DU BIST HIER ━━ divider above the next-coming events).
  4. She reads the follow-ups: "Antrag auf CMO-Überprüfung (claimant, R.333.2 · 1 Monat)" and 2 optional follow-ups. The Stichtag input in the page header defaults to today; she leaves it.
  5. She doesn't save anything — this was a quick lookup. Scratch scenario auto-persists but she doesn't name it; it'll fall off her recent list after a while.
  6. Later she identifies the matter (HL-2024-001), switches to "Aus Akte" mode, and continues there.

§2.3 Journey C — Case-file driven ("Aus Akte")

Persona: Anna, senior associate. Working on HL-2024-001 (UPC infringement). The client just confirmed they want to file a CCR.

  1. Opens /tools/procedures. Page-header Akte picker shows recent projects; she picks HL-2024-001.
  2. Page header auto-fills: proceeding = upc.inf.cfi, perspective = defendant (from projects.our_side), scenario_flags = {with_ccr: false} (current state).
  3. Builder loads: one upc.inf.cfi triplet, perspective-flipped. Event cards overlay actuals from paliad.deadlinesKlageerhebung is filed (2026-01-15), Klageerwiderung is planned (2026-04-01, computed), others are planned.
  4. She turns on with_ccr on the triplet's flag strip. The CCR child triplet expands inline. Crucially: the scenario is project-backed — the flag write also patches projects.scenario_flags (via existing PATCH /api/projects/{id}/scenario-flags from atlas P0). When she walks away, the project's deadlines + flags reflect the builder's state.
  5. She marks the Widerklage auf Nichtigkeit event card as "filed" with today's date. Builder writes a paliad.deadlines row with status='done' + completed_at=today, audit_reason "via Litigation Builder". Project's Verlauf reflects this.
  6. The CCR child triplet's Antrag Patentänderung (R.30) event card surfaces. She marks it "planned" and ticks the per-card optional horizon to "+2" → 2 more optional R.30-adjacent rules surface.
  7. Exit: she closes the tab. Project state persists in paliad.projects + paliad.deadlines as before; the scenario row tracks the builder-session view (so when she returns, the canvas state is restored — including her per-card optional-horizon picks).

§2.4 Journey D — Promote scratch to a real project

Persona: Dr. Becker, follow-up from Journey A. The client committed; she wants to convert the scenario into a real matter.

  1. With "Becker — UPC + EPA defensive" loaded, she clicks "Als Projekt anlegen" in the page header.
  2. Wizard step 1: Bestätigen. Read-only summary of what's about to be promoted: 2 proceedings (UPC inf + EPA opp), CCR child, 3 scenario flags set, 0 events filed, 5 events planned, 2 notes. "Weiter".
  3. Wizard step 2: Parteien ergänzen. Each proceeding's parties section shows whatever placeholder names she sketched in the scenario ("Klg X" / "Bekl Y"). She edits each into the real names. (Per m's Q11 pick — full carry — placeholder strings come in; the wizard's job is to clean them.)
  4. Wizard step 3: Akte-Metadaten. Case number, client, litigation parent project (optional), our_side (auto-set from the scenario's primary triplet), team selection. "Anlegen".
  5. New paliad.projects row written with origin_scenario_id = <scenario.id>. Scenario row's status flips to promoted, promoted_project_id points back. Builder navigates to /projects/<new-id>.
  6. The scenario stays read-only in her "Meine Szenarien" list under "Promoted", reachable for historical reference (cf. "this is what we planned at briefing time").

§2.5 Journey E — Share a scenario with a colleague

Persona: Anna shares the HL-2024-001 builder session with Dr. Becker (her supervising partner) for review before committing to the CCR strategy.

  1. Anna opens the scenario, clicks "Teilen" in the page header.
  2. Side panel slides in with a user-picker (HLC user search). She picks "Dr. Becker", clicks "Schreibgeschützt teilen".
  3. paliad.scenario_shares row written. Anna remains sole editor.
  4. Dr. Becker opens the tool. Her side panel "Meine Szenarien" has a new bucket "Geteilt mit mir"; Anna's scenario is listed. She opens it: canvas renders the same view but every mutating affordance (add proceeding, flag toggle, file/skip, promote, share) is disabled. Watermark: "Geteilt von Anna · schreibgeschützt".
  5. Becker reads, drops Anna a note via existing comment infrastructure (out of scope — separate ticket). Decision made out-of-band. Anna proceeds.

§3 The canvas shape

§3.1 ASCII sketch

┌─────────────────────────────────────────────────────────────────────────────────────┐
│ Paliad · Verfahren & Fristen — Litigation Builder            [Mein Konto ▾]          │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Szenario: [Becker — UPC + EPA def.   ▼]  Gespeichert ✓ ·  [Benennen] [Teilen] [Als Projekt] │
│ Akte:     [— ohne — ▼]    Stichtag: [2026-04-01]                                     │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Filter:   [🔍 Klageerwiderung, Hinweis, HL-2024…           ]                          │
│           Forum [● UPC] [DE] [EPA] [DPMA]   Verfahren [● upc.inf.cfi …]              │
│           Partei [Klg] [● Bekl]   Ereignisart [filing] [hearing] [decision]          │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Einstieg:  [ Übersicht ● ][ Ereignis ○ ][ Aus Akte ○ ]                               │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│   ┌─ upc.inf.cfi · Verletzungsverfahren UPC  Bekl-Sicht [▾]   [Detailgrad: Gewählt ▾]│
│   │  Optionen: ☑ with_ccr  ☐ with_amend  ☐ with_cci                          [─][×] │
│   ├─────────────────┬────────────────────┬─────────────────────────────────────────┤
│   │ Proaktiv (Bekl) │       Gericht      │ Reaktiv (Klg)                           │
│   │ ┌─────────────┐ │                    │ ┌─────────────┐                         │
│   │ │ Klageerw.   │ │                    │ │ Klageerh.   │                         │
│   │ │ R.23        │ │                    │ │ R.13        │                         │
│   │ │ planned     │ │                    │ │ filed       │                         │
│   │ │ 2026-04-01  │ │                    │ │ 2026-01-15  │                         │
│   │ │ +3 Optionen ▾│ │                    │ │             │                         │
│   │ └─────────────┘ │                    │ └─────────────┘                         │
│   │                  │ ┌──────────────┐  │                                          │
│   │                  │ │ Mündl. Verh. │  │                                          │
│   │                  │ │ planned      │  │                                          │
│   │                  │ │ [Gericht]    │  │                                          │
│   │                  │ └──────────────┘  │                                          │
│   │   ━━━━━━━━━ DU BIST HIER (Klageerwiderung) ━━━━━━━━━                            │
│   └─────────────────┴────────────────────┴─────────────────────────────────────────┘ │
│                                                                                      │
│   ┌── (spawn child) upc.ccr.cfi · Widerklage auf Nichtigkeit   Klg-Sicht [▾] ───────┐│
│   │  Optionen: ☐ with_amend                                                  [─][×]││
│   ├────────────────────┬─────────────────┬───────────────────────────────────────┐ ││
│   │ Proaktiv (Klg)     │     Gericht     │ Reaktiv (Bekl)                        │ ││
│   │ ┌─────────────┐    │                 │                                       │ ││
│   │ │ CCR-Antrag  │    │                 │                                       │ ││
│   │ │ R.49        │    │                 │                                       │ ││
│   │ │ planned     │    │                 │                                       │ ││
│   │ └─────────────┘    │                 │                                       │ ││
│   └────────────────────┴─────────────────┴───────────────────────────────────────┘ ││
│                                                                                      │
│   ┌─ epa.opp.opd · Einspruchsverfahren EPA  PatInh-Sicht [▾]  [Detailgrad: Gewählt ▾]│
│   │  Optionen: (keine flags für EPA Opp)                                     [─][×] │
│   ├─────────────────┬────────────────────┬─────────────────────────────────────────┤
│   │ Proaktiv        │       EPA          │ Reaktiv (Einsprechende)                 │
│   │ ┌─────────────┐ │                    │                                          │
│   │ │ Erwiderung  │ │                    │                                          │
│   │ │ R.79(1) EPÜ │ │                    │                                          │
│   │ │ planned     │ │                    │                                          │
│   │ └─────────────┘ │                    │                                          │
│   └─────────────────┴────────────────────┴─────────────────────────────────────────┘ │
│                                                                                      │
│   [ + Verfahren hinzufügen ]                                                         │
│                                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────┘

  Side panel (collapsible, right-edge):
  ┌──── Meine Szenarien ────┐
  │ ● Aktiv                  │
  │   ▸ Becker — UPC+EPA def │ ← current
  │   ▸ Test-CCR-Patent-X    │
  │ ○ Geteilt mit mir        │
  │   ▸ Becker UPC ply       │
  │ ○ Promoted               │
  │   ▸ HL-2023-118          │
  │ ○ Archiviert (3)         │
  │ [+ Neues Szenario]       │
  └──────────────────────────┘

§3.2 What each element does

Element Read Write Persists in
Page-header scenario picker Current scenarios.id + name Switch scenarios URL ?scenario=<id> + DB
[Benennen] button Anonymous → named scenarios.name, status='active' DB
[Teilen] button scenario_shares row(s) DB
[Als Projekt] button Opens promote wizard (wizard → DB on commit)
Akte picker User's projects Loads project state into builder URL ?project=<id> + DB
Stichtag input Scenario-level default scenarios.stichtag DB
Filter strip (search + chips) Free-text + dimension filters UI state URL ?q, ?forum, … per-mode
Einstieg mode radio Current entry mode Resets filter strip on change URL ?mode=
Triplet header (jurisdiction badge + name + perspective + Detailgrad) scenario_proceedings.{primary_party, detailgrad} Edit DB
Triplet flag strip scenario_proceedings.scenario_flags Toggle flags DB
Event card (state, date, notes, optional-horizon) scenario_events.* Edit per-card DB
+ Verfahren hinzufügen New scenario_proceedings row DB
Side panel User's scenarios + shared scenarios Switch + create + archive DB

§3.3 Columns: proaktiv | court | reaktiv

The 3-column layout returns as the canonical desktop shape. Per m's locked constraint (and brief #153), it is a stance grouping, not a sequence anchor — time flows top-to-bottom (chronological), columns express who is acting.

  • Proaktiv: the column for events the active perspective's party initiates (their primary_party matches the event's primary_party).
  • Court: court-set events (is_court_set=true), neutral column.
  • Reaktiv: the column for events the opposing party initiates.

The perspective is per-proceeding (per-triplet, via scenario_proceedings.primary_party). When no perspective is set (null), both party columns render equally with their natural party labels (Klg / Bekl), not Proaktiv / Reaktiv. This means kontextfrei browsing reads as "claimant column | court | defendant column" until the user picks a side.

This addresses m's reverted-design bug #3 verbatim: "Proaktiv/Gericht/Reaktiv columns are a stance grouping, not a sequence anchor." Time = vertical. Stance = horizontal. The triplet is the unit; multiple proceedings stack vertically.

§3.4 Event card anatomy

┌─────────────────────┐
│ Klageerwiderung     │  ← event name (procedural_event.name)
│ R.23                │  ← rule code
│ planned             │  ← state: planned / filed / skipped
│ 2026-04-01          │  ← date (computed for planned, actual for filed)
│ +3 Optionen ▾       │  ← per-card optional horizon (only when card has optionals)
└─────────────────────┘

State machine (m's Q10 pick — 3-state):

  • planned (default): future event, date is computed from anchor + duration_value + duration_unit. Click → choose filed or skipped.
  • filed: past event, actual_date is set (defaults to computed, user can override). Visual: ✓ checkmark, slightly muted "past" tone.
  • skipped: user chose not to file. Visual: strikethrough text + optional skip_reason (textarea). Optional rules are commonly skipped without rationale; mandatory rules with skipped state flag the scenario as "non-standard" but don't block.

No overdue state — user does the date arithmetic by eye against today. (Mandatory cards rendered in red when actual_date < today AND state=planned is a render hint, not a stored state.)

Per-card optional horizon (m's Q4 pick). Each card with children at priority IN ('optional','recommended-skip-by-default') carries a chip +N Optionen ▾. Default N=0 (hidden). Clicking opens an inline list of the optional children with +/- controls to surface/hide them on the canvas. Per-card horizon persists as scenario_events.horizon_optional int.

Filed-state cards persist the date in scenario_events.actual_date date. The card's notes field (textarea, lazy-loaded) lives in scenario_events.notes text.

§3.5 Court-set events

is_court_set=true rules don't compute a date until the court picks one. Card renders with [Gericht] badge in place of the date and a small "Datum eintragen" affordance. Clicking filed opens a date picker (date is required for filed state when is_court_set=true — the user is asserting "the court set this date").

Downstream events that anchor on a court-set event render their dates as [abhängig von <event>] until the court date is filed, then auto-recompute.

§3.6 Spawn (child) proceedings

When a triplet has a with_<flag> enabled and the flag's gating rule has is_spawn=true, the child proceeding (e.g. upc.ccr.cfi for with_ccr on upc.inf.cfi) renders inline as a child triplet immediately below the parent triplet in the canvas stack — visually nested via the spawn note in the parent triplet's header band.

scenario_proceedings.parent_scenario_proceeding_id FK self-references for the nesting; scenario_proceedings.spawn_anchor_event_id points at the gating sequencing_rule so the UI knows where in the parent the spawn happened.

The child triplet has its own perspective, scenario flags, Stichtag override, Detailgrad. It can itself spawn (depth N supported; today's data is 2-deep at most).

Cross-proceeding peer triggers (upc.inf judgment → epa.opp choice deadline) are out of scope for v1 (m's Q14 pick). v1 ships independent triplets stacked vertically; the user mentally tracks cross-dependencies. A future scenario_event_links table is the path to peer triggers in v1.1.


§4 Hard decisions table — m's 20 picks

# Topic Pick Locks
Q1 Modular meaning "doesn't super apply" — drop modular as a load-bearing goal §0.2
Q2 Tab state semantics Shared anchor + Akte across modes; filters reset per mode §3.1, §3.2, §6
Q3 Case-file integration Page-header Akte picker, persistent across modes §3.1, §3.2, §2.3
Q4 Optional-display horizon Per-event-card §3.4
Q5 Builder shape Unified builder, 3 entry modes (cold-open / event-triggered / Akte) §0, §1, §2, §3
Q6 Scenario↔project relationship Separate paliad.scenarios table + promote-to-project action §5, §2.4
Q7 Scenario contents Multi-proceeding constellation per scenario §3, §5
Q8 Save model Auto-save active scenario + "Meine Szenarien" list §1, §3, §6.4
Q9 Multi-proceeding render Vertical stacked column-triplets §3
Q10 Per-event state 3-state: planned / filed / skipped (no overdue state) §3.4
Q11 Promote-to-project carry Everything (incl. placeholder parties + free-form notes) §2.4, §5.4
Q12 Sharing model Private by default + explicit team-share (read-only) §1, §5, §2.5
Q13 Scenario flags placement Per-proceeding (each triplet owns its scenario_flags) §5.1
Q14 Cross-proceeding peer triggers Out of scope for v1 (defer to v1.1) §3.6, §7
Q15 Perspective scope Per-proceeding (each triplet has its own primary_party) §3.3, §5.1
Q16 Add-proceeding flow + Verfahren hinzufügen button below the last triplet, inline picker §3, §3.1
Q17 Cold-open canvas Empty canvas + "Neues Szenario" CTA + recent-list §2.1, §3
Q18 Search scope Universal: events + scenarios + Akten, scoped by result type §3.1, §6
Q19 Promote-to-project flow 3-step wizard (Bestätigen → Parteien ergänzen → Akte-Metadaten) §2.4, §5.4
Q20 Mobile treatment Desktop v1, mobile basic-read (mutating actions prompt "Auf größerem Bildschirm öffnen") §3, §7

§4.1 Divergences from inventor recommendations

Three picks diverged from my recommendation. Captured here so future readers (m, the coder) see the current design, not the strawman.

  • Q1 — Modular. Inventor recommended "plug-in widgets". m: "I don't know — generally does not super apply here." Modular is dropped as a goal; the natural decomposition (BuilderCanvas → ProceedingTriplet → EventCard → ScenarioListPanel → PromoteWizard) is documented in §6.2 as build hygiene, not as a load-bearing constraint.
  • Q10 — Event state. Inventor recommended 4-state (planned / filed / skipped / overdue). m picked 3-state — no overdue enum. Rationale (interpreted): overdue is derived from date < today AND state=planned, not stored; this avoids stale state when the date is edited.
  • Q11 — Promote carry. Inventor recommended carrying procedural shape + flags + filed-state + notes but not placeholder parties/case_number/billing. m picked "everything carries" — placeholder parties come in. Mitigation: Q19's 3-step wizard's step 2 (Parteien ergänzen) gives the user a chance to clean placeholders before commit, so the safety net m wanted on Q11 is folded into Q19.

§4.2 Inventor picks not formally asked

A few decisions are inventor-set because they're either: (a) implementation details that don't change the architecture, or (b) clean defaults that match existing patterns. Listed here so they're visible; m can flag any.

  • Detailgrad ("Gewählt" / "Alle Optionen") scope: per-proceeding (matches today's Verfahrensablauf pattern). State in scenario_proceedings.detailgrad.
  • Akte picker shape: flat dropdown sorted by recently-viewed first, with a typeahead filter for case numbers/names. Same shape as today's project picker on /agenda.
  • Notes: per-event-card (textarea on each card, lazy-loaded). Scenario-level notes also exist (scenarios.notes text) for cross-cutting commentary.
  • Read-only shared state UI: every mutating affordance is disabled (greyed, no click handlers). Watermark "Geteilt von · schreibgeschützt" at the top of the canvas. No "Fork to my workspace" affordance in v1.
  • URL contract: minimal, view-state only — ?scenario=<id>&mode=<entry>&event=<sequencing_rule_id> (deep-link to a specific anchor). Filter pills + chip state get URL params per active entry mode but explicitly NOT the constellation data (per m's "not every constellation in URL" guidance). The constellation lives in paliad.scenario_* tables.
  • Auto-save granularity: debounced 500ms on every change. Indicator near scenario name: Gespeichert ✓ (last successful save < 5s ago), Speichert… (in flight), Letzte Speicherung fehlgeschlagen — erneut versuchen (on error).
  • Soft delete: archived scenarios stay in DB with status='archived'. No hard delete in v1.
  • Audit: no audit log on scenario edits (they're exploratory). Audit on promote-to-project goes via the existing projects.audit_log.
  • Concurrent editing: single-editor model. Owner is sole editor; shares are read-only. No locking / merge conflict UI needed in v1.
  • Bilingual: German primary, English via existing i18n.ts. Scenario names: user-chosen, any language. Skip reasons + notes: free-text, any language.

§5 Data model deltas

All new tables live in paliad.* schema, alongside existing paliad.projects / paliad.deadlines / paliad.sequencing_rules.

§5.1 New tables

-- Scenario header. One row per saved scenario (named or scratch).
CREATE TABLE paliad.scenarios (
  id                   uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  owner_id             uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  name                 text NOT NULL DEFAULT 'Unbenanntes Szenario',
  status               text NOT NULL DEFAULT 'active'
                            CHECK (status IN ('active','archived','promoted')),
  origin_project_id    uuid NULL REFERENCES paliad.projects(id) ON DELETE SET NULL,
    -- set when scenario was exported from a project
  promoted_project_id  uuid NULL REFERENCES paliad.projects(id) ON DELETE SET NULL,
    -- set when scenario was promoted to a project
  stichtag             date NULL,
    -- scenario-level default Stichtag; per-triplet overrides take precedence
  notes                text NULL,
    -- free-form scenario-level commentary
  created_at           timestamptz NOT NULL DEFAULT now(),
  updated_at           timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX scenarios_owner_status_idx ON paliad.scenarios(owner_id, status);
CREATE INDEX scenarios_updated_idx ON paliad.scenarios(owner_id, updated_at DESC);

-- One row per proceeding inside a scenario. Multiple per scenario for
-- multi-proceeding constellations. parent_scenario_proceeding_id self-refs
-- for spawned children (CCR child of UPC inf etc.).
CREATE TABLE paliad.scenario_proceedings (
  id                                uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  scenario_id                       uuid NOT NULL REFERENCES paliad.scenarios(id) ON DELETE CASCADE,
  proceeding_type_id                uuid NOT NULL REFERENCES paliad.proceeding_types(id),
  primary_party                     text NULL
                                         CHECK (primary_party IN ('claimant','defendant')),
    -- per-proceeding perspective; null = no perspective picked yet
  scenario_flags                    jsonb NOT NULL DEFAULT '{}'::jsonb,
    -- per-proceeding flags: {with_ccr: true, with_amend: false, …}
  parent_scenario_proceeding_id     uuid NULL REFERENCES paliad.scenario_proceedings(id) ON DELETE CASCADE,
    -- self-ref for spawned children (CCR child of UPC inf etc.)
  spawn_anchor_event_id             uuid NULL REFERENCES paliad.sequencing_rules(id),
    -- which rule of the parent caused this spawn (for UI placement)
  ordinal                           int NOT NULL DEFAULT 0,
    -- stack order on canvas (top to bottom)
  stichtag                          date NULL,
    -- per-proceeding Stichtag override; falls back to scenarios.stichtag
  detailgrad                        text NOT NULL DEFAULT 'selected'
                                         CHECK (detailgrad IN ('selected','all_options')),
  appeal_target                     text NULL,
    -- applies_to_target for appeal proceedings; null for non-appeal triplets
  collapsed                         boolean NOT NULL DEFAULT false,
    -- user-collapsed triplet header (UI state)
  created_at                        timestamptz NOT NULL DEFAULT now(),
  updated_at                        timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX scenario_proceedings_scenario_idx ON paliad.scenario_proceedings(scenario_id, ordinal);
CREATE INDEX scenario_proceedings_parent_idx ON paliad.scenario_proceedings(parent_scenario_proceeding_id);

-- One row per event card on the canvas. Captures the card's state +
-- per-card attributes (filed date, skip reason, notes, optional horizon).
-- Most cards are sequencing-rule-backed; free-form events have a null
-- sequencing_rule_id and a non-null procedural_event_id (or text label).
CREATE TABLE paliad.scenario_events (
  id                                uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  scenario_proceeding_id            uuid NOT NULL REFERENCES paliad.scenario_proceedings(id) ON DELETE CASCADE,
  sequencing_rule_id                uuid NULL REFERENCES paliad.sequencing_rules(id),
  procedural_event_id               uuid NULL REFERENCES paliad.procedural_events(id),
    -- one of {sequencing_rule_id, procedural_event_id, custom_label} must be set
  custom_label                      text NULL,
    -- free-form event name when neither sequencing_rule nor procedural_event apply
  state                             text NOT NULL DEFAULT 'planned'
                                         CHECK (state IN ('planned','filed','skipped')),
  actual_date                       date NULL,
    -- set when state='filed'; can also be set for state='planned' (court-set override)
  skip_reason                       text NULL,
    -- optional rationale when state='skipped'
  notes                             text NULL,
    -- per-card free-form
  horizon_optional                  int NOT NULL DEFAULT 0,
    -- per-card "show N more optionals" affordance
  created_at                        timestamptz NOT NULL DEFAULT now(),
  updated_at                        timestamptz NOT NULL DEFAULT now(),
  UNIQUE (scenario_proceeding_id, sequencing_rule_id) WHERE sequencing_rule_id IS NOT NULL
);

CREATE INDEX scenario_events_proceeding_idx ON paliad.scenario_events(scenario_proceeding_id);

-- Read-only team shares. Owner is sole editor; shares grant view-only.
CREATE TABLE paliad.scenario_shares (
  id                       uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  scenario_id              uuid NOT NULL REFERENCES paliad.scenarios(id) ON DELETE CASCADE,
  shared_with_user_id      uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  created_at               timestamptz NOT NULL DEFAULT now(),
  created_by               uuid NOT NULL REFERENCES auth.users(id),
  UNIQUE (scenario_id, shared_with_user_id)
);

CREATE INDEX scenario_shares_user_idx ON paliad.scenario_shares(shared_with_user_id);

§5.2 Additions to existing tables

-- One nullable FK on paliad.projects to track which scenario spawned this
-- project (set on promote-to-project). Auditable origin trail.
ALTER TABLE paliad.projects
  ADD COLUMN origin_scenario_id uuid NULL
                  REFERENCES paliad.scenarios(id) ON DELETE SET NULL;

CREATE INDEX projects_origin_scenario_idx ON paliad.projects(origin_scenario_id)
  WHERE origin_scenario_id IS NOT NULL;

No other changes to existing schema. paliad.deadlines continues to be the authoritative source for project-bound actuals; the builder writes to paliad.deadlines (not scenario_events) when working in Akte mode against a project-backed scenario.

§5.3 RLS

Same pattern as existing paliad.projects:

  • scenarios readable by owner_id OR by users with a matching scenario_shares.shared_with_user_id row.
  • scenarios writable only by owner_id (and only when status != 'promoted').
  • scenario_proceedings + scenario_events cascade from scenario visibility.
  • scenario_shares readable by shared_with_user_id or created_by; writable only by the scenario owner.

Helper function paliad.can_see_scenario(scenario_id) mirrors the existing paliad.can_see_project(project_id) shape.

§5.4 Promote-to-project: data flow

[Wizard step 1: Bestätigen]
  Read: scenarios + scenario_proceedings + scenario_events
  Action: none (read-only summary)

[Wizard step 2: Parteien ergänzen]
  Read: scenario_proceedings.scenario_flags (for hints about placeholder party names)
  Action: builds an in-memory parties payload (per proceeding, per role)

[Wizard step 3: Akte-Metadaten]
  Read: user's clients + litigations + project tree (existing /projects API)
  Action: builds an in-memory project metadata payload

[Commit]
  Transaction:
    1. INSERT into paliad.projects (carrying step-2 + step-3 payloads, + scenario notes)
       SET origin_scenario_id = <scenario.id>
    2. INSERT into paliad.parties from step-2 payload
    3. For each scenario_proceeding (depth-first, parent before child):
       a. INSERT scenario_flags as projects.scenario_flags (parent-level only;
          children become sub-projects via parent_project_id)
       b. For each filed scenario_event: INSERT paliad.deadlines row with
          status='done', completed_at=actual_date, audit_reason='via Litigation Builder promotion'
       c. For each planned scenario_event: INSERT paliad.deadlines row with
          status='pending', due_date=computed (or actual_date override)
       d. Skipped events: not inserted (no deadline row)
    4. UPDATE paliad.scenarios SET status='promoted', promoted_project_id=<new>
    5. Navigate to /projects/<new>

The deadlines write uses existing POST /api/projects/{id}/deadlines/bulk semantics under the hood — no new bulk-deadline-from-scenario endpoint needed.


§6 Modular boundaries (light)

m said modular "doesn't super apply" — dropped as a load-bearing goal. The natural decomposition below is build-hygiene documentation, not a constraint the coder must enforce.

§6.1 Front-end components

Component File Responsibility
BuilderCanvas frontend/src/components/BuilderCanvas.tsx Root render of the builder. Receives the active scenario, renders triplet stack + cold-open empty state
ProceedingTriplet frontend/src/components/ProceedingTriplet.tsx One proceeding's render: header strip (jurisdiction + name + perspective + Detailgrad + collapse + remove) + flag strip + 3 columns + spawn child triplets recursively
EventCard frontend/src/components/EventCard.tsx One card in a column lane. State / date / optional-horizon / notes affordances
ScenarioFlagsStrip frontend/src/components/ScenarioFlagsStrip.tsx Per-triplet flag toggles. Reads scenario_flag_catalog, applies to scenario_proceedings.scenario_flags
AddProceedingPicker frontend/src/components/AddProceedingPicker.tsx Inline picker triggered by + Verfahren hinzufügen. Forum chip row → Verfahren chip row → Hinzufügen
ScenarioListPanel frontend/src/components/ScenarioListPanel.tsx Side panel: Aktiv / Geteilt / Promoted / Archiviert buckets + new-scenario CTA
PromoteToProjectWizard frontend/src/components/PromoteToProjectWizard.tsx 3-step modal: Bestätigen / Parteien / Metadaten
PageHeaderControls frontend/src/components/PageHeaderControls.tsx Scenario picker + Benennen/Teilen/Promote buttons + Akte picker + Stichtag input
EntryModeChrome frontend/src/components/EntryModeChrome.tsx Cold-open / event-triggered / Akte mode radio; ephemeral UI affordance that fades into canvas state

§6.2 Client TS files

Mirror the React-ish component split:

  • frontend/src/client/builder.ts — root orchestrator (auto-save loop, URL state, mode routing, scenario fetch)
  • frontend/src/client/builder-scenario.ts — scenario CRUD against /api/scenarios
  • frontend/src/client/builder-event-card.ts — per-card state machine + optional-horizon control
  • frontend/src/client/builder-promote-wizard.ts — 3-step wizard state machine
  • frontend/src/client/builder-search.ts — universal search (events + scenarios + Akten)
  • frontend/src/client/builder-shares.ts — share-with-team UI

§6.3 Backend services + routes

Service File Endpoints
ScenarioService internal/services/scenario_service.go List / Get / Create / Update / Archive / Promote
ScenarioProceedingService internal/services/scenario_proceeding_service.go Add / Remove / Update (flags, perspective, ordinal, detailgrad)
ScenarioEventService internal/services/scenario_event_service.go List / Update state / Set date / Set notes / Set horizon
ScenarioShareService internal/services/scenario_share_service.go List / Add / Remove shares
ScenarioPromoteService internal/services/scenario_promote_service.go Wizard-driven transactional promote

Routes (added under existing API namespace):

GET    /api/scenarios                               — list user's scenarios (filtered by status)
POST   /api/scenarios                               — create new scenario
GET    /api/scenarios/{id}                          — get scenario + proceedings + events (deep)
PATCH  /api/scenarios/{id}                          — update name / stichtag / notes / status
DELETE /api/scenarios/{id}                          — archive (soft delete; status='archived')
POST   /api/scenarios/{id}/proceedings              — add proceeding to scenario
PATCH  /api/scenarios/{id}/proceedings/{pid}        — update flags / perspective / ordinal / detailgrad
DELETE /api/scenarios/{id}/proceedings/{pid}        — remove proceeding (cascades to events)
PATCH  /api/scenarios/{id}/events/{eid}             — update state / date / notes / horizon
POST   /api/scenarios/{id}/shares                   — share with user (read-only)
DELETE /api/scenarios/{id}/shares/{sid}             — revoke share
POST   /api/scenarios/{id}/promote                  — promote to project (3-step wizard payload)
POST   /api/scenarios/from-project/{project_id}     — export project to a new scenario (what-if)
GET    /api/search                                  — universal search (events + scenarios + Akten)

Existing endpoints used unchanged:

  • GET /api/tools/fristenrechner/search?kind=events — for the events corpus.
  • GET /api/projects — Akte picker source.
  • POST /api/projects/{id}/deadlines/bulk — promotion writes deadlines through this.
  • PATCH /api/projects/{id}/scenario-flags — Akte-mode flag sync.

§7 Migration plan from current live shape

Current live (/tools/procedures on main @ ed3c5d1) = cronus's U0-U4 4-tab catalog. Migration is a 6-slice train, every slice ships visibly. No feature flag (m's pattern preference per #152 Q7).

§7.1 Slice train

Slice What ships DB Visible to user
B0 — Scenario DB foundation New tables (scenarios + scenario_proceedings + scenario_events + scenario_shares) + RLS + minimal API (list / create / get). Scenarios writable from a developer-only test route at first. Mig #N (new tables + RLS + paliad.projects.origin_scenario_id) No user-visible change.
B1 — Builder shell + cold-open mode New /tools/procedures page replaces the 4-tab catalog. Renders: page header (scenario picker + Akte picker + Stichtag + search), entry-mode radio (cold-open active), filter strip, empty canvas + "Neues Szenario starten" CTA + recent list. Add-proceeding picker works; first triplet renders with the existing Verfahrensablauf-core calc. Auto-save active scenario. Side panel "Meine Szenarien" with Aktiv bucket only. New page visible. Single triplet works end-to-end.
B2 — Multi-triplet + spawn nesting + per-event state Vertical multi-triplet stack with + Verfahren hinzufügen. Per-triplet perspective + flag strip. Spawn child triplets render inline. Event cards get the 3-state machine (planned/filed/skipped) + date editor + per-card optional horizon chip. Page-header Stichtag drives default dates. Full scenario builder works without Akte integration.
B3 — Event-triggered mode + universal search "Ereignis" entry mode wires the search box to land on a single-triplet anchored view (scratch scenario). Universal search returns events + scenarios + Akten with type-scoped result groups. Filter pills (forum/proc/party/kind) reset on mode switch. Event lookup works.
B4 — Akte mode + project-backed scenarios "Aus Akte" entry mode + page-header Akte picker. Loads project state into the builder (proceeding + perspective + scenario_flags + deadlines actuals). Akte-backed scenarios write through to paliad.deadlines + paliad.projects.scenario_flags; non-Akte scenarios write to paliad.scenario_events. Cross-surface scenario-flag-changed event listener reused from #152 T3. Akte integration works end-to-end.
B5 — Share + Promote-to-project wizard "Teilen" button + user picker + share row. "Geteilt mit mir" bucket in side panel. "Als Projekt anlegen" opens the 3-step wizard (Bestätigen → Parteien ergänzen → Akte-Metadaten). Successful commit creates project + cascades deadlines + sets origin_scenario_id, navigates to /projects/{id}. "Promoted" bucket in side panel. Sharing + promotion work.
B6 — Mobile basic-read + cleanup + i18n polish Mobile (<640px) shows scenarios + cards read-only; mutating affordances prompt "Auf größerem Bildschirm öffnen". Cleanup: delete dead U0-U4 catalog code (4-tab control, legacy verfahrensablauf.ts, etc.). All i18n keys finalised (DE + EN). Mobile works; codebase cleaner.

§7.2 Why this train shape

  • B0 is DB-only. The schema can land independently and be exercised via test routes / Supabase MCP before any UI sees it. Keeps mig risk isolated.
  • B1-B2 are the MVP. After B2, a user can build and save a multi-proceeding scenario fully kontextfrei. That alone replaces 60% of today's catalog use.
  • B3 adds the lookup path. After B3, "what's next after Klageerwiderung?" works without saving.
  • B4 makes it real. Akte integration is the load-bearing piece for daily use; ships once the foundation is stable.
  • B5 unlocks team value. Sharing + promotion are the difference between "personal tool" and "team tool". Ship after the core works.
  • B6 is cleanup. Mobile read + dead code removal land last to avoid coupling to in-flight features.

§7.3 What stays unchanged

  • URL /tools/procedures keeps it (the new builder lives there).
  • Sidebar entry "Verfahren & Fristen" keeps it.
  • cmd-K palette keeps it.
  • /tools/fristenrechner + /tools/verfahrensablauf legacy redirects (from cronus's U4) stay alive: 301 → /tools/procedures (the builder).
  • pkg/litigationplanner.CalculateRule — untouched.
  • /admin/procedural-events — untouched.
  • /projects/{id} Verlauf — untouched (new "Im Builder öffnen" button is the only addition).

§7.4 Cleanup at B6

Dead code to delete (verify with grep before deletion):

  • frontend/src/components/VerfahrensablaufBody.tsx (replaced by ProceedingTriplet)
  • frontend/src/client/verfahrensablauf.ts (replaced by builder.ts orchestration)
  • frontend/src/client/views/verfahrensablauf-state.ts (replaced by scenario-backed state)
  • frontend/src/client/views/verfahrensablauf-state.test.ts
  • frontend/src/client/verfahrensablauf-detail-mode.ts — KEEP. Builder imports filterByDetailMode from it; per-triplet Detailgrad reuses this module.
  • Existing scratch tab content in frontend/src/client/procedures.ts (4-tab toggling logic, mode routing)

Kept:

  • frontend/src/client/views/verfahrensablauf-core.ts (calculation engine; reused by EventCard + ProceedingTriplet)
  • Legacy URL redirects in Go (/tools/fristenrechner + /tools/verfahrensablauf/tools/procedures)

§8 Open follow-ups (out of scope for v1)

Tracked for v1.1 / future tickets:

  • Cross-proceeding peer triggers (UPC-inf judgment → EPA opp choice deadline). New paliad.scenario_event_links table. UI: trigger-picker chip on event cards.
  • DE / EPA / DPMA full expansion. v1 supports EPA + DPMA proceedings at the data layer (calc engine handles them), but the spawn flags and CCR-style nestings are UPC-specific. Other jurisdictions get proper coverage in v1.1.
  • Scenario versioning / snapshots. m's Q8 alternative ("versioned snapshots") deferred. Add when scenarios start driving client briefings.
  • Multi-user concurrent editing. Out of scope. Single-editor model with read-only shares is sufficient until usage shows otherwise.
  • Fork-a-shared-scenario. Read-only sharing in v1 doesn't expose "fork into my workspace". Add when team usage demands it.
  • Comments on scenarios / event cards. Out of scope (separate ticket).
  • PDF export of a scenario for client briefings. Out of scope.
  • Mobile-parity edits. v1.1 — full mobile interaction loop.
  • Audit log on scenario edits. Out of scope (exploratory data).
  • Cross-scenario comparison view. ("Compare planned vs actual" lives on the project page via promote-then-compare; explicit comparison tool is v2.)

  • mBrian: file as [synthesis] linked triggered_by t-paliad-339; related_to atlas's reverted tracker design, cronus's unified-procedural-events-tool design, atlas's deadline-system-revision.
  • Cross-refs in this repo: docs/design-procedures-workflow-tracker-2026-05-27.md (atlas, reverted), docs/design-unified-procedural-events-tool-2026-05-27.md (cronus, live), docs/design-deadline-system-revision-2026-05-27.md (atlas Phase 2), docs/design-fristenrechner-overhaul-2026-05-26.md (cronus 2026-05-26).
  • Gitea: m/paliad#153 (this PRD), m/paliad#152 (atlas's tracker, reverted), m/paliad#151 (cronus U0-U4 shipped), m/paliad#149 (atlas Phase 2 in flight).
  • Coder phase (deferred per inventor SKILL): runs after m ratifies this PRD. Slice ordering per §7.1. NOT edison (parked at DESIGN READY FOR REVIEW). NOT atlas (just-rejected tracker → framing bias). NOT cronus (parked on Fristenrechner inventor branch). A pattern-fluent Sonnet coder picks up B0 first.

§10 Coder hand-off notes

(Pre-emptive — for whoever picks up B0.)

  • Migration number: check internal/db/migrations/ for the max slot at coder shift start. Two recent migrations (curie's t-paliad-336, ritchie's t-paliad-149 P0) are in flight; coordinate via paliadin/head before claiming a slot.
  • Akte integration nuance: when the builder is in Akte mode and the scenario is project-backed, writes flow to paliad.deadlines / paliad.projects.scenario_flags instead of paliad.scenario_* tables — the scenario row itself just records the canvas view-state (which triplets are visible, ordinal, collapsed state, per-card horizon). This dual-write rule is the load-bearing complexity of B4; design tests for it explicitly.
  • Auto-save throttling: 500ms debounce per change. Avoid PATCH-per-keystroke on notes textareas (use blur-trigger + 2s debounce there).
  • Search performance: universal search (events + scenarios + Akten) needs to stay snappy. Events corpus is ~3000 rows; scenarios/Akten are per-user. Use existing trgm indexes; avoid joining across all three for ranking.
  • B5 transactional promotion: do the wizard's commit in a single Postgres transaction. If any of (project insert / parties / deadlines / scenario status update) fails, roll back atomically. No partial promotions.
  • Mobile rendering: B6 is meant to be cheap. Column-triplet → CSS grid that collapses to single-column at @media (max-width: 640px). Mutating affordances get pointer-events: none + a click-handler that surfaces the "Auf größerem Bildschirm öffnen" toast — keeps the desktop interaction code paths unchanged.
  • i18n keys: every user-facing string gets data-i18n from B1. Don't accumulate i18n debt across slices.