# Design — Determinator B1 row-by-row cascade (replaces breadcrumb drilldown)

**Author:** pauli (inventor)
**Date:** 2026-05-13
**Task:** t-paliad-166
**Status:** READY FOR REVIEW — m gates inventor → coder transition.
**Gitea:** m/paliad#25 (re-opened by m's 2026-05-13 11:17 comment).

---

## 0. Premises verified live (before designing)

CLAUDE.md, mai-memory and the task brief can all be stale by days. Every anchor below is verified against the live codebase or live DB on `mai/pauli/determinator-b1-row-by` (baseline `adf377c` — main as of Slice 1 of t-paliad-179 merge).

### 0.1 The Pathway B markup today

`frontend/src/fristenrechner.tsx:227-310` is the Pathway B shell. Four functionally different layers are stacked with four visually different treatments. Live, in source order:

| Layer | Element | Affordance | Visual |
|---|---|---|---|
| **L1 Mode** | `.fristen-mode-toggle` | `role=radiogroup` with two `<input type="radio">` | Radio buttons. Tree vs Filter. |
| **L2 Perspective** | `.fristen-perspective-bar` | Three `<button>` chips | Pill chips. Kläger / Beklagter / Beide. |
| **L3 Inbox** | `.fristen-inbox-bar` | Four `<button>` chips | Pill chips. CMS / beA / Posteingang / Alle. |
| **L4 Cascade** | `.fristen-b1-cascade` | Breadcrumb + question + button-grid (drill-down) | Cards in a grid, breadcrumb above. |

Below L4 sits `.fristen-b1-results` — the concept-card list that narrows as the cascade descends. That's content, not a decision layer.

**m's critique is exact:** L1/L2/L3/L4 are all "narrow the deadline-rule space" steps with the same conceptual weight, but the user sees a radio, two pill strips, and a card grid. The cascade itself (L4) hides previous steps behind a breadcrumb — so when you've drilled three levels deep you can no longer see "I picked CMS → vom Gericht → Hinweisbeschluss" in one glance unless you read tiny breadcrumb crumbs.

### 0.2 The cascade engine today

`frontend/src/client/fristenrechner.ts:2405-2574` (`renderB1Cascade`). For a given `?b1=<slug>`:

1. Build `trail = buildBreadcrumb(roots, currentSlug)`. The trail is the ancestors of the current node.
2. Render `<nav class="fristen-b1-breadcrumb">` = root-reset + `›`-separated crumb buttons.
3. Render `<p class="fristen-b1-question">` = the current node's `step_question_de` (or `"Was ist passiert?"` at root).
4. Render `<div class="fristen-b1-buttons">` = child nodes as button cards (icon + label, `--leaf` modifier on terminal nodes).
5. Render `<button class="fristen-b1-step-back">` = "← Eine Stufe zurück".

Drilling = `navigateB1(child.slug)` = `pushState` + `renderB1Cascade(child.slug)`. The previous question disappears; only the breadcrumb crumb survives as text. **There is no "row of answered decisions."**

### 0.3 Where narrowing happens today

`fristenrechner.ts:2509-2522` filters cascade children by two predicates before rendering:

- `inboxFilterAllowsForums(c.forums)` — hides nodes whose `forums` tag doesn't match `activeForumOnPage()`. The active forum is resolved at `fristenrechner.ts:2960-2970` with a three-input precedence chain:
  1. **Inbox chip** (`cms` → `upc`, `bea` / `posteingang` → `de`). User override beats everything.
  2. **Ad-hoc chip** from Step 1's explore-mode bypass (`upc` / `de` / `epa` / `dpma`).
  3. **Project context** (`project.proceeding_type_id` → `proceeding_types.code` → prefix → `upc` / `de` / `epa` / `dpma`).
- `perspectiveAllowsParty(c.party)` — hides leaves whose `party` tag contradicts the perspective chip. t-paliad-164 already auto-fills the chip from `project.our_side`.

**So project-driven narrowing for the FORUM axis is shipped.** What m is asking for in this task is (a) generalize the pattern so MORE rows get pre-answered, (b) make the answered-state visible in the same row format, (c) hide rows whose answer is fully implied (UPC project + L3 Inbox).

### 0.4 The taxonomy and rule corpus

Live data, `paliad.event_categories` (recursive tree, t-paliad-133):

- **6 root buckets** under `(root)`: `cms-eingang` ("Von wem ist das Schriftstück?"), `muendl-verhandlung` ("Mündliche Verhandlung"), `beschluss-entscheidung` ("Beschluss / Entscheidung"), `frist-verpasst` ("Frist verpasst"), `ich-moechte-einreichen` ("Ich möchte etwas einreichen"), `sonstiges` (terminal leaf).
- **103 leaves total.** 91 carry a `forums` tag (`upc` / `de` / `epa` / `dpma`); 12 are neutral. 16 leaves carry a `party` tag — all under `ich-moechte-einreichen.*` (claimant / defendant) — the perspective filter touches outgoing filings only, never incoming Gegenseiten-Schriftstücke (which are symmetric: you receive what the other side sent regardless of who you are).
- Cascade depth varies 2–4 levels. Slug encodes the path with dots, e.g. `cms-eingang.gegenseite.upc-inf.klageschrift` is 4 segments deep.

`paliad.proceeding_types`:

- **20 `category='fristenrechner'` codes** (the wizard / B1 cascade vocabulary): `UPC_INF`, `UPC_REV`, `UPC_APP`, `UPC_APP_ORDERS`, `UPC_COST_APPEAL`, `UPC_DAMAGES`, `UPC_DISCOVERY`, `UPC_PI`, `DE_INF`, `DE_INF_OLG`, `DE_INF_BGH`, `DE_NULL`, `DE_NULL_BGH`, `DPMA_OPP`, `DPMA_BPATG_BESCHWERDE`, `DPMA_BGH_RB`, `EPA_OPP`, `EPA_APP`, `EP_GRANT`.
- **7 `category='litigation'` codes** (the project model's vocabulary): `INF`, `REV`, `CCR`, `APM`, `APP`, `AMD`, `ZPO_CIVIL`. All `jurisdiction='UPC'` except `ZPO_CIVIL`.
- **The two vocabularies overlap conceptually but not row-wise.** Mapping `litigation_code × jurisdiction → fristenrechner_code` is required for Akte-derived narrowing beyond the 4-letter forum prefix. The brief lists this mapping; the live data confirms it's the only path.

`paliad.deadline_rules.condition_flag` — 4 distinct flag-sets live in production: `[with_amend]`, `[with_cci]`, `[with_ccr]`, `[with_ccr, with_amend]`. Only on `UPC_INF` and `UPC_REV`. This is a Determinator-style variant axis the cascade does not surface today; out of scope for this design.

### 0.5 Live state of `paliad.projects`

| Column | Live data shape | Used by today's cascade? |
|---|---|---|
| `court` | **Free-text.** 4 non-null values across 4 rows: `LG München I` (1), `UPC` (2), `UPC CoA` (1). 7 rows NULL. | No. |
| `proceeding_type_id` | FK → `proceeding_types.id`. **11/11 live rows are NULL.** | Yes — `forumFromProject` reads it, but it never fires in production today. |
| `our_side` | enum `claimant` / `defendant` / `both` / `court` / NULL. | Yes — t-paliad-164 perspective chip predefine. |
| `counterclaim_of` | uuid FK self-reference. | No (relevant for SmartTimeline, not Determinator). |
| `filing_date` / `grant_date` | dates. | No (relevant to Verfahrensablauf wizard). |

**Critical caveat:** 11/11 live projects have NULL `proceeding_type_id`. Until that's backfilled (a separate cleanup), Akte-driven narrowing degrades to "no opinion" for every existing project. The design honours this — silent degrade, no failed-load toast, the cascade simply doesn't narrow. m locked this v1 behaviour with kelvin on 2026-05-13.

### 0.6 Anchor files for the implementer

- `frontend/src/fristenrechner.tsx:227-310` — Pathway B markup (the four-layer mess).
- `frontend/src/client/fristenrechner.ts:2405-2574` — `renderB1Cascade`.
- `frontend/src/client/fristenrechner.ts:2914-3081` — forum + perspective narrowing engine (`activeForumOnPage`, `inboxFilterAllowsForums`, `perspectiveAllowsParty`, `applyOurSidePredefine`).
- `frontend/src/styles/global.css:1636-1822` — `.fristen-pathway-shell`, `.fristen-mode-toggle`, `.fristen-b1-breadcrumb`, `.fristen-b1-question`, `.fristen-b1-buttons`, `.fristen-b1-button`, `.fristen-b1-step-back` (the visuals this design overhauls).
- `frontend/src/styles/global.css:1965-2065` — `.fristen-inbox-bar`, `.fristen-perspective-bar`, `.fristen-inbox-chip` (the chip strip rules).
- `frontend/src/client/views/verfahrensablauf-core.ts` (t-paliad-179) — pure-functional core, verified to carry **zero** Pathway B / cascade code. The lift is clean; this design is independent of it.

### 0.7 Adjacent design docs

- `docs/design-tools-cleanup-2026-05-12.md` (kelvin, t-paliad-178). Slice 1 of that shipped today; Slice 2 (Step 0 toggle + Akte auto-derivation on `/tools/fristenrechner`) is adjacent and will share the `litigation_code × jurisdiction → fristenrechner_code` mapping with this design.
- `docs/research-determinator-coverage-2026-05-08.md` (curie, t-paliad-167). Identified leaves missing from the cascade. Out of scope here — this design is the UX shell that any future coverage additions will land into.

If any of these conflict with what the task brief or memory asserts, **the live state wins** and the brief is the bug — flagged in §13 for m.

---

## 1. Vision + the three pillars

m's framing (2026-05-13 11:17):

> When I select a project, it should already narrow down the options (at least if it is a court proceeding). If it is a UPC proceeding, there is no need to show "non-UPC options"; this starts with the "how did you receive it?" which - for the UPC - will always be the UPC CMS.
>
> Not only is the different format for the levels of the questions weird (this needs an overhaul!), also there is no narrowing at all. I already described before that I want each decision on the tree to remain visible (one row per decision, it may be more compact than the active question was) and then go through things until there are only the least possible options left.

Three pillars, intertwined:

### Pillar 1 — Project-driven narrowing
Pre-fill or hide decision rows whose answer is implied by the project. UPC project → "Wo kam es an?" is implied (CMS). Project with `our_side` → perspective implied. Project with `proceeding_type_id` → cascade root narrows to the matching forum (and deeper, if mappable).

### Pillar 2 — Visual hierarchy overhaul
All decision layers are **the same primitive**: a row with a question label, an answer-area, and an inline "ändern" affordance. Whether the layer is mode-toggle, perspective, inbox, or a cascade level, the visual shape is identical. The active layer expands inside its row; inactive (answered) layers compact to a single line.

### Pillar 3 — Row-by-row persistent cascade
Replace breadcrumb drilldown with stacked rows. Each answered decision stays visible as a compact row. The active question is the only row that expands. The cascade builds top-to-bottom; the user sees every choice they made in one glance, and the answered rows act as their own affordances for "ändern".

The pillars interact:

- Pillar 3 (row layout) needs to know what to skip (Pillar 1 narrowing). A skipped row can render as a compact "(aus Akte) UPC CMS" pseudo-row, or be absent. We pick per row in §5.
- Pillar 2 (visual hierarchy) defines how *answered* vs *active* vs *skipped-but-shown* rows look. The four-different-treatments mess gets resolved by a single `.fristen-row` primitive.
- Pillar 1 (narrowing) also affects *initial state*: in Akte-mode, several rows may render as already-answered on page load. The cascade jumps to the first un-answered row.

---

## 2. The row primitive

The whole new layout is built from one element shape. Call it `.fristen-row` (the existing `.fristen-b1-*` class names get retired or rebased).

```text
┌─ .fristen-row ──────────────────────────────────────────────────────┐
│  .fristen-row-num   .fristen-row-label              .fristen-row-edit │
│  [1]                Wie suchen?                              [ändern] │
│  .fristen-row-body                                                   │
│  ✓ Schritt-für-Schritt                                                │
└──────────────────────────────────────────────────────────────────────┘
```

Three states:

### 2.1 `state="active"` — the user is answering this row

```text
┌─ .fristen-row.is-active ────────────────────────────────────────────┐
│  [3]   Von wem ist das Schriftstück?                                 │
│                                                                       │
│        ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│        │ ⚖️           │  │ 🏛️           │  │ ✉️            │          │
│        │ Vom Gericht   │  │ Von der      │  │ Vom Patent-   │          │
│        │               │  │ Gegenseite    │  │ amt           │          │
│        └──────────────┘  └──────────────┘  └──────────────┘          │
│                                                                       │
│        ← zurück                                                       │
└──────────────────────────────────────────────────────────────────────┘
```

Same chip-style buttons regardless of which row it is. Mode pick = two big chips. Perspective = three chips. Inbox = four chips. Cascade step = N chips, one per child node. Leaf cascade chips get a subtle modifier (`.fristen-row-chip--leaf`) so the user can see "this one ends the cascade".

### 2.2 `state="answered"` — the user has picked, but the answer is below

```text
┌─ .fristen-row.is-answered ──────────────────────────────────────────┐
│  [1]   Wie suchen?                              ✓ Schritt-für-Schritt │
│                                                              [ändern] │
└──────────────────────────────────────────────────────────────────────┘
```

Single line. The label, the picked answer, an "ändern" affordance. Click anywhere on the row (or the explicit ändern link) re-opens the row as active and drops every row below it. (This matches the existing breadcrumb-click semantic: jumping back to an ancestor invalidates descendants.)

### 2.3 `state="prefilled"` — derived from the project (or other auto-source)

```text
┌─ .fristen-row.is-answered.is-prefilled ─────────────────────────────┐
│  [2]   Ich vertrete                           ✓ Klägerseite          │
│                                       aus Akte: HL-2024-001 [ändern] │
└──────────────────────────────────────────────────────────────────────┘
```

Visually identical to `is-answered` but carries a small "aus Akte: <reference>" tag and a slightly muted background. Clicking ändern flips it to active (and drops the prefilled marker — the user has now made an explicit choice).

This generalises t-paliad-164's perspective predefine: same shape, same hint, same override-by-click semantics. The hint becomes a row-level token rather than a one-off `<span>` next to the chip strip.

### 2.4 `state="hidden"` — row is implied by an earlier pre-fill

A row that adds no information given upstream rows can be omitted entirely. e.g. UPC project → forum is `upc` → inbox row's only valid answer is "CMS" → the row simply doesn't render. We **do not** render a `is-hidden` placeholder; the absence is the affordance. (This is m's "no need to show non-UPC options".)

The first user-actionable row floats up under the prefilled stack.

### 2.5 Why one primitive

The current four-layer mess works against m because each layer looks like a different *kind* of question. The row primitive collapses that: every decision row carries the same "label + answer + ändern" anatomy. The user reads top-to-bottom; the answered rows stack as a paper trail; the active row is the only thing that demands interaction.

This also implicitly solves the row-count tax of m's "see your selections" ask: the rows compact to ~28px each when answered, so even a deep cascade keeps the active question in the upper third of the viewport.

---

## 3. Answered / active / prefilled / hidden — visual treatment

Concrete CSS sketch (Slice 1 will tune; this is the contract):

| Token | Active | Answered | Prefilled | Hidden |
|---|---|---|---|---|
| `min-height` | auto (chips wrap) | `28px` | `28px` | 0 (not rendered) |
| `background` | `var(--surface-card)` | `transparent` | `color-mix(var(--color-accent) 4%, transparent)` | n/a |
| `border-left` | `4px solid var(--color-accent)` | none | `4px solid var(--color-accent-faded)` | n/a |
| `font-weight` (label) | 600 | 500 | 500 | n/a |
| `font-weight` (answer) | n/a | 600 | 600 | n/a |
| `cursor` | default | pointer (whole row) | pointer (whole row) | n/a |
| `ändern` affordance | hidden | shown on hover + always on focus-within | always shown | n/a |
| Row number badge | accent-filled | outlined | outlined (faded) | n/a |

**No `::before { inset: 0 }` overlay tricks.** The whole-row click is wired via a JS handler that calls `reopenRow(idx)` and skips clicks on `<a>` / `<button>` inside the row body — same pattern as `.entity-table` and the project-detail Verlauf items (CLAUDE.md anchor under "Whole-card / whole-row click").

Active vs answered transition: when the user picks an answer in an active row, the row collapses to `is-answered` and the **next un-prefilled row materialises as active**. The DOM is preserved across the transition (row stack is one container with `data-state` attribute switched on each row); the chip set inside the answered row replaces with the single ✓-prefixed answer span.

For the prefilled state's "aus Akte: <reference>" tag — reference comes from `project.reference` (e.g. `HL-2024-001`), falling back to the first 8 chars of `project.id` if no reference. Click on the reference tag is a navigation shortcut to the project (open in new tab — keeps the Fristenrechner state intact).

---

## 4. Project-driven narrowing — data mapping

What can we derive from a selected project, and where does each derivation land?

### 4.1 Mapping table

| Derivation | Source column(s) | Maps to | Pre-fills row | Hides row? |
|---|---|---|---|---|
| **Forum** (upc / de / epa / dpma) | `proceeding_type_id` → `proceeding_types.code` prefix. Fallback: `court` free-text contains UPC/LG/OLG/BGH/BPatG/EPA/DPMA. | Cascade filter (existing `inboxFilterAllowsForums`). | "Wo kam es an?" if forum=UPC (→ CMS). DE: prefills nothing (beA vs Posteingang is a Postal Realität, not on the project). | UPC: yes. DE/EPA/DPMA: no. |
| **Perspective** | `project.our_side` ∈ {claimant, defendant} | Cascade filter (existing `perspectiveAllowsParty`). | "Ich vertrete" → Klägerseite / Beklagtenseite. `both` / `court` / NULL: no prefill. | No — even when prefilled, the row stays visible (the user needs to see "ah yes, I'm the Beklagte here"). |
| **Proceeding type** | `proceeding_type_id` + jurisdiction → fristenrechner code via `mapLitigationToFristenrechner()` (new helper, shared with t-paliad-178 Slice 2) | Cascade depth: prunes root buckets that don't apply, and prunes inner buckets to those matching the proceeding code. e.g. UPC + INF → only `cms-eingang.gegenseite.upc-inf.*`, `cms-eingang.gericht.urteil-upc-cfi`, etc. | Pre-collapses cascade sub-branches; surfaces deeper-leaf rows directly when only one path applies. | Hides intermediate cascade rows whose only child matches the derived code. |
| **Counterclaim** | `counterclaim_of IS NOT NULL` | Implies `with_ccr` / `with_cci` condition flag context. | Not a cascade row today — surfaces as a `condition_flag` chip on the wizard. **Out of scope for this design**; flagged in §13 Q6. | n/a |
| **Filing / grant dates** | `filing_date`, `grant_date` | Wizard anchor pre-fill. | Not a cascade row. Out of scope. | n/a |

### 4.2 Detail: the litigation → fristenrechner mapping

t-paliad-178 §0 and the task brief both call out: `project.proceeding_type_id` points at the **7 `litigation` codes** (INF, REV, CCR, APM, APP, AMD, ZPO_CIVIL). The cascade speaks **`fristenrechner` codes** (UPC_INF, DE_INF, ...). A small mapping is needed:

```text
INF + UPC      → UPC_INF
INF + DE       → DE_INF        (first instance; OLG/BGH not derivable from project)
REV + UPC      → UPC_REV
REV + DE       → DE_NULL
CCR + UPC      → UPC_INF + condition_flag=[with_ccr]  (linked via parent's proceeding)
CCR + DE       → DE_NULL              (German Nichtigkeit IS the counterclaim equivalent)
APP + UPC      → UPC_APP
APP + DE       → DE_INF_OLG | DE_NULL_BGH  (ambiguous — needs court or instance hint; degrade)
APM + UPC      → UPC_PI
AMD + UPC      → UPC_INF + condition_flag=[with_amend]
ZPO_CIVIL + DE → ZPO civil only; ignore for cascade (no fristenrechner code)
```

The mapping lives in **one** place — a new `internal/services/proceeding_mapping.go` (or the same shared helper t-paliad-178 Slice 2 introduces). The frontend gets the **resolved fristenrechner code** plus `condition_flag` array as part of the project payload (`ProjectOption.derived_fristenrechner_code` + `.derived_condition_flags`).

**Honest about degrade:** the mapping isn't always 1:1. APP+DE is ambiguous, ZPO_CIVIL has no analogue, and projects without `proceeding_type_id` (all 11 live ones today) get no derivation at all. The cascade falls back to forum-only narrowing in every ambiguous case. **Never silent FK promotion.**

### 4.3 Detail: court free-text fallback

When `proceeding_type_id` is NULL but `court` has a recognisable substring:

```text
court contains "UPC"            → forum=upc
court contains "BPatG"          → forum=de  (Nichtigkeit / DPMA-Beschwerde)
court contains "BGH"            → forum=de
court contains "OLG"            → forum=de
court contains "LG"             → forum=de
court contains "EPA" / "EPO"    → forum=epa
court contains "DPMA"           → forum=dpma
otherwise                       → no narrowing
```

This is a UX nicety, not a correctness mechanism. The fuzzy match always loses to a real `proceeding_type_id` if both are set. Surfaces as the prefilled-row reference tag: "Forum: UPC (aus Gericht: UPC CoA)".

### 4.4 What the cascade hides given a forum

`event_categories.forums` is the live signal:

- 91/103 leaves carry a forum tag.
- 12 are neutral (cross-cutting: `frist-verpasst`, `sonstiges`, some Mündl-Verhandlung leaves, court actions).

With `forum=upc` active, ~73 leaves drop from the cascade. The user sees the same root buckets (cms-eingang / muendl / beschluss / frist-verpasst / ich-moechte-einreichen / sonstiges) but each bucket's children list collapses to the upc-relevant subset. **This is already wired today; the design doesn't change the filter, only its visual presentation.**

The new contribution: when a non-leaf bucket reduces to a single descendant chain (e.g. UPC project → `cms-eingang` → `gegenseite` → `upc-inf` is the only chain), the cascade should optionally **auto-walk** the chain and surface the leaf parent's siblings directly. §5 below.

### 4.5 What the cascade hides given perspective

Currently only the 16 `ich-moechte-einreichen.*` leaves carry `party` tags. So perspective filters outgoing-filing nodes only. Incoming `cms-eingang.gegenseite.*` nodes don't have party tags — receiving from the opposing side is symmetric (you receive what they sent, regardless of who you are). This is correct and doesn't need fixing.

**Design implication:** the perspective row is *always* visible (rows can never be `is-hidden` based on perspective alone), even when prefilled, because its filter affects user-write decisions that the user might still want to override. Match t-paliad-164.

---

## 5. What gets pre-answered, hidden, or skipped-but-shown

A concrete matrix per row, given live data + the rules above:

| Row | Question | Pre-fill source | UPC project | DE project | EPA / DPMA project | No project (ad-hoc) | No project (zero ctx) |
|---|---|---|---|---|---|---|---|
| **R0 Mode** | Wie suchen? | none | active | active | active | active | active |
| **R1 Perspective** | Ich vertrete | `project.our_side` | prefilled iff `our_side` ∈ {claimant, defendant}; else active | same | same (rare for EPA/DPMA — usually only `court` or NULL) | active | active |
| **R2 Inbox** | Wo kam es an? | forum derivation | **hidden** (forum=upc ⇒ CMS implied) | active (beA vs Posteingang) | active | active | active |
| **R3 Bucket** | Was ist passiert? | none — user always picks the bucket | active | active | active | active | active |
| **R4..Rn Cascade** | per-node `step_question_de` | proceeding-code derivation can pre-walk a single-child chain | optionally auto-walks single-child chains | same | same | active | active |

Notes:

- **R0 Mode**: kept active in all cases. The user always picks Tree vs Filter (or skips R0 entirely if we ditch the mode toggle — see §6). The mode pick is meta and not derivable from the project.
- **R1 Perspective**: a project with `our_side='both'` is rare but legitimate; it lands as active. `'court'` is even rarer (m's project model includes a "we are the court" perspective for hypothetical training scenarios). For now: `court` → active row.
- **R2 Inbox**: m's literal ask. UPC → hidden. DE → active (because beA vs Posteingang is meaningful for downstream Phase-0 manual workflows even if the cascade filter doesn't care). EPA/DPMA → active (e.g. EPA online filing vs Post). The "Alle" chip stays for "I don't know yet".
- **R3 Bucket**: the 6 root buckets are always shown. Even with a derived proceeding code, the user still has to say "I'm here because I received something / mündl. Verhandlung / Urteil / etc." This is too coarse to derive.
- **R4..Rn Cascade auto-walk**: when a derived proceeding code reduces a bucket's children to a single chain, the cascade should pre-walk that chain. e.g. UPC + INF + `cms-eingang` bucket → only `gegenseite.upc-inf.*` chain survives → R4 `gegenseite` is pre-answered (with the "aus Akte" badge), R5 jumps directly to `upc-inf` (also pre-answered), and R6 is the active question "Welcher Schriftsatz?". The user sees four R-rows (R0, R1 prefilled, R3 picked, R4 prefilled, R5 prefilled, R6 active) — clean paper trail of inference + one active question.

  **Important constraint:** auto-walk is **descendants-of-the-picked-bucket only**. R3 (bucket) is always active because the bucket is the user's intent. We never auto-pick the bucket. So a UPC project doesn't pre-pick "cms-eingang" for you; it just makes the sub-cascade efficient once you've said "cms-eingang".

### 5.1 Compact summary diagram — UPC INF project drilling into a cms-eingang opposing-side schriftsatz

```text
┌─ Step 1: Akte (Step 1 surface, above Pathway B) ────────────────────┐
│ Akte: HL-2024-001 — Acme v. Globex (UPC INF)        [Andere Akte]   │
└─────────────────────────────────────────────────────────────────────┘
┌─ [1] Wie suchen?                       ✓ Schritt-für-Schritt [ändern]┐
└─────────────────────────────────────────────────────────────────────┘
┌─ [2] Ich vertrete                      ✓ Klägerseite          [ändern]┐
│                                                  aus Akte: HL-2024-001│
└─────────────────────────────────────────────────────────────────────┘
                                ⨯  Row R2 (Inbox) hidden — UPC implies CMS
┌─ [3] Was ist passiert?                 ✓ CMS-Eingang          [ändern]┐
└─────────────────────────────────────────────────────────────────────┘
┌─ [4] Von wem ist das Schriftstück?    ✓ Von der Gegenseite    [ändern]┐
│                                          aus Akte (UPC INF impliziert)│
└─────────────────────────────────────────────────────────────────────┘
┌─ [5] Welches Verfahren?               ✓ UPC Verletzungsverfahren     │
│                                          aus Akte: HL-2024-001       │
└─────────────────────────────────────────────────────────────────────┘
┌─ [6] Welcher Schriftsatz wurde eingereicht?    (active, awaiting pick)│
│                                                                       │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐    │
│  │ Klageschrift     │  │ Klageerwiderung  │  │ Replik           │    │
│  │ (R.13)           │  │ + Widerklagen    │  │                  │    │
│  └──────────────────┘  └──────────────────┘  └──────────────────┘    │
│  ... (rest of UPC_INF Schriftsätze)                                   │
│                                                                       │
│  ← zurück                                                              │
└─────────────────────────────────────────────────────────────────────┘
```

Six rows. Three user picks (mode, bucket, leaf). Three Akte-derived prefills. One R2 absent. The user sees their full decision path at a glance.

For comparison, today's UI: the user clicks four times into the cascade, the top of the page is two chip strips and a radio they didn't touch, the breadcrumb at the top of `.fristen-b1-cascade` shows three crumb buttons in 12pt text, and there's no inline indication that the cascade is narrower than the full taxonomy. m's "no narrowing at all" is the literal reading of what's visible.

### 5.2 Compact summary diagram — DE project drilling into the same

```text
┌─ [1] Wie suchen?                       ✓ Schritt-für-Schritt [ändern]┐
└─────────────────────────────────────────────────────────────────────┘
┌─ [2] Ich vertrete                      ✓ Klägerseite          [ändern]┐
│                                                  aus Akte: HL-2024-002│
└─────────────────────────────────────────────────────────────────────┘
┌─ [3] Wo kam es an?                                (active, awaiting pick)┐
│                                                                       │
│  ┌──────┐  ┌──────────────┐  ┌──────┐                                 │
│  │ beA  │  │ Posteingang  │  │ Alle │                                 │
│  └──────┘  └──────────────┘  └──────┘                                 │
└─────────────────────────────────────────────────────────────────────┘
... and the cascade continues below once R3 is answered.
```

R2 (Inbox) is active because beA vs Posteingang is a real distinction for German projects. The forum is already known (`de`), so the cascade below R3 will be DE-only — but the user still tells us *how* the document arrived.

### 5.3 Compact summary diagram — abstract / no-Akte mode

```text
┌─ [1] Wie suchen?                                  (active, awaiting pick)┐
│                                                                       │
│  ┌────────────────────────┐  ┌─────────────────────┐                  │
│  │ Schritt-für-Schritt    │  │ Filter / Suche      │                  │
│  │ (Entscheidungsbaum)    │  │                     │                  │
│  └────────────────────────┘  └─────────────────────┘                  │
└─────────────────────────────────────────────────────────────────────┘
```

No prefills, no hidden rows. Every row is asked. Full taxonomy.

---

## 6. Filter / Suche mode — coexistence with the cascade

Today's mode toggle (radio) is a UX wart: it's the only radio on the page, it looks unlike everything else, and it sits at the top of Pathway B as if it were a primary axis.

Two options to fold it into the row model:

### Option A — Mode is R0, a row like any other

The mode toggle becomes the first row in the stack. Two chips. Pick determines what populates below: tree picker → R3 + cascade. Filter picker → R3 collapses into a search input + result list. The row stays visible (you can switch mid-flow via ändern), but the chrome is consistent.

Pros: simple, every decision is a row, the page reads top-to-bottom.
Cons: adds one always-active row to every flow including the "I know what I'm doing, just give me search" use case.

### Option B — Mode is an escape hatch, not a row

Filter is positioned as "ich weiß schon, wonach ich suche" — a small link / icon at the top of Pathway B that toggles between cascade and search. No R0 row. Default = cascade. Click → search replaces the row stack.

Pros: fewer rows, less for the common case to scan past.
Cons: more discoverable than the current radio? unclear. "Where did the radio go?" is a question.

### Option C — Filter as a *bottom-of-stack* affordance

Cascade is the only top-down flow. Below the cascade results, a "Sie wissen schon den Namen? → direkt suchen" link / row appears. Search is a graceful fallback, not a peer mode.

Pros: gives cascade the primary surface, search becomes a tool for "wait, I know better".
Cons: discoverability of search is reduced for power users who DO know.

**Inventor's pick:** Option B. The radio is dead weight, and the search use case is "I know the name; let me skip the cascade" — that's an escape hatch, not a peer axis. Visually: a small `🔍` icon-button at the top-right of Pathway B titled "Direkt suchen". Click expands a search input that replaces the row stack; result list appears below; "← Zurück zum Entscheidungsbaum" returns to the row stack with prior state preserved.

But this is design-question territory — m's call. §13 Q1.

---

## 7. Mobile + responsive

The row primitive is naturally responsive: rows stack vertically by default. Width concerns only the chip set inside an active row.

### 7.1 Breakpoints

`paliad` already uses 640 / 768 / 1023 px breakpoints. The rows live inside `.fristen-pathway-shell` which is already a column-flex.

| Width | Row chrome | Chip layout (active row) |
|---|---|---|
| ≥ 1024px | full label + answer + ändern on one line, badge left | chips in a 3-column grid (or auto-fill min 220px) |
| 768–1023px | same | chips in a 2-column grid |
| 640–767px | label + answer on line 1, ändern on line 2 right-aligned | chips in a 1-column stack |
| < 640px | label on line 1, answer on line 2, ändern as `›` icon right-aligned | chips full-width, single column |

### 7.2 Active-row collapse on tap (mobile-only)

On `< 768px`, the row stack scrolls; the active row's chip set can be long (e.g. 9 Schriftsatz children). When the user picks an answer, the page autoscrolls so the next active row is at the top of the viewport. This is the same pattern as the Akte picker (Step 1) and existing form flows.

### 7.3 What we don't do on mobile

- **No drawer / modal for the cascade.** The whole point of the row stack is being able to see history at a glance; collapsing into a separate surface defeats it.
- **No fly-out for ändern.** Tap on an answered row's ändern affordance simply re-activates the row in place.
- **No "next" button.** Picking a chip advances automatically; mobile doesn't need an extra tap to confirm.

---

## 8. "Neu starten" / Reset semantics

Three flavours of reset, all need a home:

### 8.1 Reset the whole cascade (every row to empty)

Today: clicking the breadcrumb's "Pfad zurücksetzen" root crumb. In the new layout: a small `↺ Pfad zurücksetzen` link at the top of the row stack, right of the heading. Clicking it:
- Drops every cascade row (R3+).
- Leaves R0 (Mode), R1 (Perspective prefilled), R2 (Inbox if visible) as they are — those are "context", not "the user's investigation".
- Re-activates R3.

Optional behaviour (per Q9): a confirm-dialog if the user has drilled ≥ 3 cascade levels deep. Probably overkill; current breadcrumb root-click is destructive without confirm. Match existing semantic.

### 8.2 Drop just one decision (ändern semantic)

Built into every answered row's `[ändern]` affordance and clicking on the row body. Effect: that row reverts to active; every row below it drops; URL ?b1= shortens to that row's prefix.

This is the workhorse of the row stack — m's "you can see your selections" UX implies "you can also rewind to any of them at any time". Built-in.

### 8.3 Drop the Akte-derived prefills

Trickier: if the user clicks ändern on a `is-prefilled` row, the prefill is overridden. But what about "I want to ignore my Akte entirely for this exercise"? The Akte itself is bound at the Step 1 surface, above Pathway B. Clicking "Andere Akte" at the Step 1 summary unbinds the Akte and drops all `is-prefilled` markers. The cascade rows that were `is-answered` because they were prefilled now revert to `is-active` (or, if the user had already explicitly overridden via ändern, stay answered with no `is-prefilled` flag).

This semantic already half-exists for t-paliad-164's perspective predefine; we generalise it to every prefilled row. Implementation: hold a `prefillSources: Map<rowID, "akte" | "user">` and re-derive on Akte unbind / change.

### 8.4 The "Neu starten" button at the bottom

A second affordance at the bottom of the results area, after the user has reached a leaf and is reading concept-cards. "Andere Frist nachschlagen?" → reset to R3. Optional but discoverable; today's UI lacks an equivalent, so this is a small UX win.

---

## 9. Search affordance integration

Tied to §6's mode-toggle question. Two integration points:

### 9.1 Search panel placement (Option B from §6)

The `🔍 Direkt suchen` button lives at the top-right of `.fristen-pathway-shell`. Click → animates the row stack out (or simply replaces it), shows a search input row with a single text field + result list below. ESC or "← Zurück zum Entscheidungsbaum" returns; row stack restores via URL state.

The search is the existing `?q=` + B2 chips flow — we don't rebuild it, just relocate its entry point. Existing forum-filter chip row stays inside the search panel.

### 9.2 Inline search on each cascade row (rejected)

An alternative: each cascade row's chip list gets a tiny "filter chips" input at the top. Reject. Adds chrome to every active row for a feature most users don't need.

### 9.3 "I searched but want to see the path" round-trip

When the user lands on a leaf via search, optionally show "Im Entscheidungsbaum öffnen → " — clicking restores the row stack with all ancestor rows pre-answered (which is what the cascade's slug already encodes). This is a small extra: lets a search-first user verify "yes, this is the leaf I thought, here's the proceeding context I missed".

---

## 10. Slicing for the coder pass

Three slices, each independently shippable, mergeable in order:

### Slice 1 — Visual hierarchy + row-by-row layout (no narrowing change)

Replaces the four-layer mess with the row primitive. **No backend or DB changes.** The narrowing engine stays the same (existing forum + perspective filters fire); the visual presentation moves from breadcrumb + chip strips + radio → row stack.

In scope:
- New `.fristen-row` CSS primitive (with `.is-active`, `.is-answered`, `.is-prefilled` modifiers).
- Refactor `renderB1Cascade` into a row-stack renderer (`renderRowStack(rows: RowSpec[])`).
- Migrate L1 (mode) / L2 (perspective) / L3 (inbox) / L4..n (cascade) all to row instances.
- "ändern" semantic = re-activate row, drop rows below, push history state.
- Reset link at top of stack.
- i18n keys for row labels.

Out of scope for Slice 1:
- Project-derived proceeding-code narrowing (the `mapLitigationToFristenrechner` helper).
- Auto-walk single-child cascade chains.
- Hide-R2-on-UPC behaviour (Slice 2 — needs the proceeding mapping helper anyway).
- Search affordance relocation (Slice 3).

Outcome: same data, same narrowing, **vastly better visual narrative**. The user can finally see their decision path. m's pillar 2 + 3 are addressed.

### Slice 2 — Project-driven narrowing depth

Adds the `litigation_code × jurisdiction → fristenrechner_code` mapping and uses it to:
- Pre-fill the proceeding-type sub-cascade rows (R5 in the §5.1 diagram).
- Hide R2 (Inbox) when project is UPC.
- Auto-walk single-child chains.
- Add the "aus Akte: <reference>" tag on prefilled rows.

This is where Pillar 1 fully lands. Depends on Slice 1's row primitive.

Includes a small backend helper (shared with t-paliad-178 Slice 2 if both ship in parallel): `internal/services/proceeding_mapping.go` exposes `MapLitigationToFristenrechner(litCode string, jurisdiction string) (fristenCode string, conditionFlags []string, ok bool)`.

Outcome: an Akte-bound user starts the cascade with three rows already answered, and only one or two active questions remain to drill to the leaf.

### Slice 3 — Search affordance + mobile polish

Relocates the mode-toggle / search affordance per §6 Option B. Adds the responsive breakpoints from §7. Polishes the autoscroll-to-active behaviour on mobile.

Mobile-only fixes ride here so Slices 1+2 can be reviewed by m at desktop width first.

### Why this order

- Slice 1 is purely visual. m can see the row stack and validate the layout BEFORE we change any narrowing semantic. If m hates the row primitive, we revert one PR. (We won't — but the option matters.)
- Slice 2 is the heavy correctness lift. It depends on the mapping helper, on Akte payload extensions, and on careful Test_DATABASE_URL integration tests.
- Slice 3 is final polish. Independently mergeable, lowest risk.

Each slice is roughly:
- Slice 1: 1 frontend PR (~700 LoC TSX + CSS + client). No backend, no migrations.
- Slice 2: 1 mixed PR (~150 LoC Go + 300 LoC client). No migrations.
- Slice 3: 1 frontend PR (~150 LoC).

---

## 11. Tradeoffs flagged

### 11.1 Row stack is taller than the current shell

A deep cascade (4 levels) plus 3 prefilled rows + R0 = 8 rows. Each ~28px compact + the active row's chip body (200–400px depending on chip count) + spacing → ~600–800px tall. The current shell is ~400px tall in the same scenario. Mitigation: rows are compact (28px), active-row autoscrolling keeps the chip set in view on mobile, and the visual narrative wins. m's ask explicitly trades vertical space for visibility.

### 11.2 "Aus Akte" tags are slightly noisy

Three rows showing "aus Akte: HL-2024-001" reads a bit redundant. Mitigation: only the first prefilled row shows the reference; subsequent rows show "(aus Akte)" without the reference. Saves vertical noise, keeps the source visible once.

### 11.3 Auto-walk single-child chains can confuse

The user picks "cms-eingang" → suddenly two rows materialise pre-answered. Looks magical. Mitigation: the two rows are clearly `is-prefilled` with an "aus Akte (UPC INF impliziert)" tag, and ändern is available on each. After the user has done it twice, the inference becomes a feature; before, a tooltip on first-render ("Diese Schritte ergeben sich aus Ihrer Akte") could help (deferred for v2 — see Q11).

### 11.4 Removing the radio mode-toggle is a behavioural change

Existing power users may know the radio. Mitigation: the new `🔍 Direkt suchen` icon-button at the top of Pathway B is a visible affordance; URL ?mode=filter still works as deep-link. Soft transition.

### 11.5 11/11 live projects have NULL `proceeding_type_id`

Slice 2's narrowing literally doesn't fire in production today. We're building UX that requires data nobody has yet. Mitigation: graceful degrade (forum-only narrowing via court free-text fuzzy match — already a feature today). Backfill of `proceeding_type_id` is a separate follow-up (see Q13).

### 11.6 The mapping table in §4.2 has ambiguities

APP+DE → ambiguous; ZPO_CIVIL → no analogue; CCR ↔ counterclaim modeling is fragile. Mitigation: every ambiguous case degrades to "no narrowing" — the row stays active rather than incorrectly pre-filled. Better silent than wrong.

### 11.7 ändern-on-an-ancestor invalidates descendants

Same as today's breadcrumb-click semantic — clicking a non-current crumb drops cascade depth. **No data is lost** (you can re-walk the cascade), but if the user was reading concept-cards at a leaf, those cards disappear. Mitigation: when ändern is clicked on an answered row, before dropping descendants, brief inline confirmation? Or just match today's behaviour (drop immediately). Inventor recommends match-today; Q12.

### 11.8 The row primitive may be over-engineered

A single visual primitive for four functionally different layers is a strong opinion. If a future cascade layer (e.g. variant chips for `condition_flag`) doesn't fit the primitive shape, we have to either extend the primitive or break the consistency. Mitigation: the primitive is shape (label + answer-area + ändern), not behaviour — variant chips fit because they're also "pick one (or several)". The contract is loose enough.

---

## 12. Files the implementer will touch (Slice 1 only)

### 12.1 Frontend

- **`frontend/src/fristenrechner.tsx:227-310`** — Pathway B markup. Replace `.fristen-mode-toggle` + `.fristen-perspective-bar` + `.fristen-inbox-bar` + `.fristen-b1-cascade` with a single `.fristen-row-stack` container. Add minimal scaffolding rows for mode / perspective / inbox / cascade-host. Keep `.fristen-b1-results` below — unchanged.
- **`frontend/src/client/fristenrechner.ts:2405-2574`** — Refactor `renderB1Cascade` into `renderRowStack(rows)`. The row spec is a discriminated union: `{kind: "mode" | "perspective" | "inbox" | "cascade", state: "active" | "answered" | "prefilled", question, options[], picked?}`. Rendering is one function per state; one switch on `kind` for the options builder.
- **`frontend/src/client/fristenrechner.ts:2914-3081`** — `inboxFilterAllowsForums` + `perspectiveAllowsParty` unchanged (Slice 1 is visual-only).
- **`frontend/src/client/fristenrechner.ts:initInboxFilter`** + perspective init — same handlers, new DOM targets.
- **`frontend/src/client/i18n.ts`** — ~20 new keys under `deadlines.row.*` (row labels, ändern affordance, prefilled tag, reset link, "next active" autoscroll-target announce).
- **`frontend/src/styles/global.css:1636-1822` + `:1965-2065`** — Retire `.fristen-mode-toggle`, `.fristen-perspective-bar`, `.fristen-inbox-bar`, `.fristen-b1-breadcrumb`, `.fristen-b1-question`, `.fristen-b1-buttons`, `.fristen-b1-button*`. Add `.fristen-row-stack`, `.fristen-row`, `.fristen-row-num`, `.fristen-row-label`, `.fristen-row-answer`, `.fristen-row-edit`, `.fristen-row-body`, `.fristen-row-chip`, `.fristen-row-chip--leaf`, `.is-active`, `.is-answered`, `.is-prefilled`.

### 12.2 Backend

No backend changes for Slice 1. The existing `/api/tools/fristenrechner/event-categories` and `/api/tools/fristenrechner/search` endpoints are unchanged.

### 12.3 Tests

- Pure-TS unit tests for `buildRowStack(currentState)` if extracted (table-driven: given URL state + Akte payload, output the RowSpec[]).
- Playwright smoke (post-deploy): land on Pathway B with `?path=b&project=<uuid>`, verify R1 prefilled with "aus Akte", R2 hidden for UPC project, ändern on R1 reopens, ändern on bucket drops cascade depth.

### 12.4 Anchoring back

t-paliad-164 perspective predefine code is the precedent. Re-read it before implementing — same hint mechanism, same override semantics, generalised.

t-paliad-178 Slice 2 (Step 0 toggle + Akte auto-derivation) is parallel; coordinate on the shared `proceeding_mapping.go` helper file (Slice 2 of this task introduces it; t-paliad-178 Slice 2 can adopt or vice versa, depending on which lands first).

---

## 13. Open questions for m

These are inventor's calls flagged for m's gate. Picking is on m, not the coder.

**Q1 — Mode-toggle disposition.** Three options in §6: (A) R0 row, (B) escape-hatch icon-button [inventor's pick], (C) bottom-of-stack affordance. Pick one or specify another.

**Q2 — UPC project: hide R2 entirely or show as compact prefilled?**
- Hide entirely (inventor's pick — matches m's "no need to show non-UPC options").
- Show as compact `[2] Wo kam es an? ✓ UPC CMS [ändern] aus Akte` row — verbose but explicit.

**Q3 — Auto-walk single-child cascade chains?**
- Yes, materialise R4..Rn-1 as prefilled (inventor's pick — strong UX, but feels magical first time).
- No, the user always picks their way down even when only one child applies (slower, more predictable).
- Yes-but-only-when-≥-2-rows-collapse (tradeoff).

**Q4 — "ändern" affordance shape on an answered row.**
- Hover-revealed link "ändern" (inventor's pick — keeps row clean by default).
- Always-visible pencil icon (more discoverable but more chrome).
- Whole-row click is the only handle (cleanest, but no visible affordance — newcomers won't discover it).

**Q5 — Drop confirmation when ändern invalidates descendants?**
- No (match today's breadcrumb-click — inventor's pick).
- Yes, when ≥ 3 cascade levels would be dropped.
- Always — even a one-row drop confirms.

**Q6 — Counterclaim awareness in the cascade.**
`project.counterclaim_of IS NOT NULL` implies `[with_ccr]` or `[with_cci]` condition flag depending on the parent's proceeding code. Should this surface as a prefilled row (e.g. "Variante: with_ccr"), or only as a backend filter on the result concept cards (silent)?
- Surface as a prefilled row (transparency — user sees the variant is active).
- Silent backend filter (no row tax, but mystery narrowing).
- Out of scope for this design — handle in a separate variant-chip task.

**Q7 — R0 mode-pick deep link.**
If a user lands on `?path=b` without `?mode=`, do we default to tree or to "no R0 picked yet"?
- Default to tree, R0 prefilled (today's behaviour — silent).
- R0 active until the user picks (more explicit, but adds one extra click for the common case).

**Q8 — Prefilled-row override permanence.**
After the user clicks ändern on a prefilled R1 (perspective) and explicitly picks "Beklagter" instead of the Akte's "Kläger", does this override persist if they re-bind the same Akte?
- No, re-bind re-applies (today's behaviour — clean, but overrides feel ephemeral).
- Yes, store override per-Akte in localStorage (sticky overrides — UX-friendly, but new state).

**Q9 — Reset confirm.**
A "Pfad zurücksetzen" link at the top of the row stack — confirm dialog?
- No confirm — match today's breadcrumb root-click (inventor's pick).
- Confirm if cascade depth ≥ 3.
- Always confirm.

**Q10 — Search escape-hatch position.**
Per §6 / §9, the `🔍 Direkt suchen` button sits at the top-right of Pathway B.
- Top-right (inventor's pick — discoverable, doesn't push down the row stack).
- Below the row stack, after results.
- As a permanent row at the bottom of the stack.

**Q11 — First-visit tooltip on auto-walked rows.**
"Diese Schritte ergeben sich aus Ihrer Akte" tooltip on the first prefilled-from-mapping row, dismissed forever on first close?
- Yes (helps onboarding).
- No (extra chrome; the "aus Akte" tag is enough).
- Inline help-icon (?) link to a docs page (longer-form).

**Q12 — Concept cards live below the row stack today. Should they collapse / hide when the user reopens an ancestor row (ändern)?**
- Collapse/hide on ändern, repopulate when the cascade reaches a leaf again (inventor's pick — matches the "no orphan content" rule).
- Keep visible as last-known until cascade resolves to a new leaf.

**Q13 — Backfill `paliad.projects.proceeding_type_id`?**
11/11 live rows are NULL. Slice 2's narrowing depends on this. Should the Slice 2 PR also include a one-off Akte-edit nudge ("Projekt-Setup vervollständigen: Verfahrensart fehlt"), or do we wait until m manually fills them in over time?
- Inline "Verfahrensart ergänzen" link on Akten with NULL proceeding_type_id.
- Backfill script (inferring from `court` free-text where unambiguous).
- Defer entirely; live with degraded narrowing until users fill it organically.

**Q14 — Reorder rows so prefilled stack at top, user-picked at bottom?**
The §5.1 diagram orders rows R0..Rn in their natural cascade sequence (mode → perspective → inbox → bucket → cascade depth). The prefilled rows happen to be R1, R4, R5 (not contiguous). Alternative: visually float all prefilled rows to a single "aus Akte" group at the top, with user-picked rows below. Tradeoff: cleaner separation vs. losing the temporal narrative of the decision path.
- Keep natural order (inventor's pick — narrative wins).
- Group prefilled at top.

**Q15 — Should `Filter / Suche` mode also see Akte prefills?**
If the user enters search mode with a project bound, do we silently scope results to the project's forum, or show the full taxonomy?
- Scope (consistent with cascade narrowing — inventor's pick).
- Don't scope (search is a "I know what I'm looking for" mode; the project is incidental).
- Scope with a visible toggle "Auch andere Foren anzeigen".

---

## DESIGN READY FOR REVIEW

Awaiting m's go/no-go on the questions in §13 before the coder shift starts. Inventor (pauli) parks after this commit — no implementation kickoff, no other-skill autoload, head gates the transition.

Recommended implementer: pattern-fluent Sonnet coder. The row primitive is straightforward CSS + a small state machine refactor; the precedent code (t-paliad-164 + t-paliad-133 cascade engine) is well-understood. **NOT cronus per memory directive 2026-05-06.**
