Selected .tracker-pill.is-active used --color-accent-fg, which in dark
mode resolves to lime → lime text on lime background, unreadable.
Switch to --color-accent-dark (midnight in both modes) so the selected
pill has midnight text on lime in both light + dark. Same pattern as
the older .filter-pill.active rule.
The workflow tracker (T1-T4) replaces every consumer of the entry-mode
modules. Verified via grep that no non-deleted file imports the
following before removal:
Deleted (10 files):
- client/fristenrechner-mode-a.ts (Mode A search panel)
- client/fristenrechner-wizard.ts (Mode B guided wizard)
- client/fristenrechner-wizard.test.ts
- client/fristenrechner-result.ts (post-commit result-view)
- client/fristenrechner-result.test.ts
- client/verfahrensablauf.ts (Verfahrensablauf panel client)
- client/views/event-card-choices.ts (per-card choice popover —
only verfahrensablauf.ts consumed it)
- client/views/verfahrensablauf-state.ts (URL + storage helpers —
only verfahrensablauf.ts consumed it)
- client/views/verfahrensablauf-state.test.ts
- components/VerfahrensablaufBody.tsx (the 4-tab proceeding picker
body — no consumer after T1)
Kept (still load-bearing):
- client/views/verfahrensablauf-core.ts — procedures-tracker uses
calculateDeadlines + CalculatedDeadline + escHtml + formatDate.
- client/views/verfahrensablauf-core.test.ts
- client/verfahrensablauf-detail-mode.ts — procedures-tracker uses
filterByDetailMode under the per-proceeding "Alle Optionen"
toggle (T4).
- client/verfahrensablauf-detail-mode.test.ts
The .css classes (.fristen-wizard-*, .verfahrensablauf-*) still live
in global.css; they're cheap orphans (no selector match in the new
DOM) and a CSS housekeeping pass is outside this train's scope. The
i18n keys (deadlines.flag.*, deadlines.detail.*, deadlines.view.*,
deadlines.side.*) likewise stay — some are used dynamically via tDyn
on the tracker, others remain candidates for a future i18n sweep.
Frontend tests: 217 pass (264 → 217, the deltas are the 3 deleted
test files: fristenrechner-result, fristenrechner-wizard,
verfahrensablauf-state). Build + go vet clean.
t-paliad-338
The final tracker layer per design §3.4 / §3.6 / §11 polish list:
- Per-proceeding "· Gewählt · / Alle Optionen" toggle (§3.4) lives in
the card header next to the show/hide button. State persists in
localStorage per proceeding code, so a page with multiple cards
can keep one expanded without affecting siblings. Toggle drives the
detail mode for filterByDetailMode + sets includeHidden=true on the
calc, so previously-skipped conditional rules re-surface muted.
- Appeal-target chip group (§3.2 #3) renders below the header on
proceedings with applies_to_target rules — today only upc.apl.unified.
Endentscheidung / Kostenentscheidung / Anordnung / Schadensbemessung
/ Bucheinsicht. Picking a target re-fetches the calc with the
appealTarget param so the timeline narrows to the matching subset.
- Cross-party muted treatment (§3.6) — when the find-header Partei
pill is set, rows whose primary_party is the opposite side render
with a "Gegen." badge and a muted style. Court / both / informational
rows are never cross-party.
- "Unselected" + "hidden" styling — under "Alle Optionen" the rules
that filterByDetailMode stamps __detailUnselected on render dotted
italic, and previously-skipped (isHidden) rules render at reduced
opacity. Honest preview of what the user is NOT considering.
- Cross-surface scenario-flag-changed listener — the tracker now reseeds
its flags state when Mode B / Verfahrensablauf / Verlauf patches the
same project's flags, so toggling there flows through here without a
refresh.
Out of T4 (court-set choices_offered chip groups and the court-set date
override from appointments) — those need a follow-up backend pass to
surface the choicesOffered payload on TimelineEntry through the calc
response in a usable shape. The data field exists on CalculatedDeadline
but isn't yet wired to a paint route on the tracker.
t-paliad-338
Wires the workflow tracker to projects via ?project=<uuid>, per design
§6.4 + §11.Q5:
- loadAkte fetches /api/projects/{id}, /api/projects/{id}/timeline
and /api/projects/{id}/scenario-flags in parallel:
1. Project title + proceeding_type — pre-seeds the Verfahren pill.
2. Timeline events → ActualsMap keyed by deadline_rule_id with
status (done / overdue / open / court_set), due / completed
date, and deadline / appointment ids.
3. scenario_flags → seeds state.flags so the gating-flag checkboxes
render in the persisted state. Per-rule rule:<uuid> flags stay
out of the calc payload (they drive priority deviations via
isRuleSelected, handled by the existing detail-mode filter).
- Auto-pin: the first render with no explicit ?event= pins the most
recent status='done' deadline. URL pin (shared link) is preserved.
- Per-node overlay: each node carries the actuals badge — ✓ (done +
strike-through), ⚠ (overdue + red wash), 📅 (open ≠ projected), ◇
(open ≡ projected). Date column shows the actual date.
- Fork write-back: PATCH /api/projects/{id}/scenario-flags fires on
every flag toggle so Mode B / Verlauf / dashboard re-render with the
same scenario on next visit. Fire-and-forget; UI doesn't wait.
- Find-header summary chips: "Akte: <title>" alongside "Anker: <name>"
+ "{n} Verfahren".
Out of T3 (deferred):
- ?project= picker UI (today's user navigates here from /projects/{id}
via deep-link).
- Per-rule rule:<uuid> flag write-back (priority deviations) — the
detail-mode filter doesn't take an interactive toggle yet.
- Cross-surface scenario-flag-changed CustomEvent listener — patching
fires the event, the tracker just doesn't yet re-render on incoming
ones (T4 polish).
t-paliad-338
Layers the anchor / focus interactivity on top of T1's shell per
design §6.1–§6.5:
- Click-to-pin (📌) on every node with a real rule_id sets the anchor.
Clicking the already-anchored pin un-pins. URL state ?event=<id>.
- Anchored node renders with a "── DU BIST HIER ──" divider beneath
its meta line + the lime left-band styling. The find-header summary
surfaces "Anker: <name>" so the user can confirm where they are.
- Fokus chip (🔍) on the anchored node toggles zoom (?zoom=1). Zoom
renders the anchor's parent chain as a breadcrumb at the top of the
proceeding card and renders only the anchored subtree below. A
"{n} weitere Schritte verborgen" footer reports what zoom hid.
- Multi-proceeding scope (§6.5): when an anchor is pinned and >1
proceeding is visible, non-anchored proceedings auto-collapse to a
one-line header card with a [zeigen] / [ausblenden] toggle. The
user's explicit expansions persist for the current anchor; pinning
a different node clears them.
- Auto-pinning from the search input (T1's single-hit behaviour) now
routes through onAnchorChanged so the multi-proc scope kicks in
consistently.
Anchor + zoom state writes through history.replaceState — sharable URL.
Un-pinning clears zoom and restores the full multi-proceeding view
automatically (lastAnchor tracking).
t-paliad-338
Direct-replace per m's Q7 divergent pick in atlas's design
(docs/design-procedures-workflow-tracker-2026-05-27.md §9): /tools/procedures
drops the 4-tab catalog (U0-U4 shipped this morning) for the single
canonical workflow-tracker shape.
T1 ships:
- Sticky find header — search input, forum / Verfahren / Partei pill
rows, global Stichtag, live result summary.
- Per-proceeding timeline cards — one card per matched proceeding,
rendered as a chained tree by parent_id with priority-styled bullets
(mandatory solid, recommended muted, optional dotted, informational
faded, court-set blue). Party badge per node.
- Cold-open default: the 6 curated proceedings from design §8 / §11.Q4
(upc.inf.cfi, upc.rev.cfi, upc.apl.unified, de.inf.lg, epa.opp.opd,
dpma.opp.dpma) render stacked with a hint above.
- Scenario-flag forks — per-proceeding "Optionen" strip on each card's
header surfaces the applicable flags (with_ccr, with_amend, with_cci)
derived from condition_expr or a fallback map. Tick re-runs the calc.
- URL state: ?q, ?forum, ?procs, ?party, ?trigger_date, ?event, ?flags.
?event= scroll-highlights the matching node (no zoom yet — T2 layers).
- Legacy ?mode= dropped silently on first state write so bookmarks
self-clean. /tools/fristenrechner + /tools/verfahrensablauf 301s
still resolve here.
Floor T1 honours: every catalog workflow it replaces — pick proceeding
(forum + Verfahren pills), search event (search input → auto-narrow +
?event= anchor), wizard narrowing (pills compose), Akte entry
(?project= read-only for T1; full overlay in T3).
Per-node fork placement (the design's stated final shape — checkbox on
the gating node itself, not a card-level strip) is a T2 refinement;
T1 keeps forks scoped per proceeding so they're not the global-page
strip m's bug #5 flagged.
Aux-proceedings inline-expandable (design §5) and the appeal-target
chip group are scoped to T4; the calculator currently doesn't surface
isSpawn / spawnProceedingCode through TimelineEntry to support them.
t-paliad-338
atlas shipped the workflow-tracker design after m's 21:01 grilling-round reframe (single timeline-with-forks, find=search+pills+result-timelines, aux inline, zoom from within full tree). 510-line doc, 2 rewrite iterations.
7 Qs answered in 2 batches (4+3). 5 on-recommendation, 2 divergent:
- Q3 (divergent): multi-proceeding anchor scope — auto-collapse other proceedings to header-only (new §6.5)
- Q7 (divergent): migration strategy — direct replace at T1, no feature flag (§9)
4-slice + cleanup train. T1 ships minimum-viable tracker visibly at /tools/procedures, replacing the catalog UI knuth shipped today.
Inventor parks. Head dispatches Sonnet coder (NOT atlas per project memory directive).
5 picks on-recommendation, 2 diverged:
Q3 (multi-proceeding anchor scope): m picked 'other timelines auto-collapse to header-only' over the recommended 'stay expanded'. Added §6.5 with the header-card render rule.
Q7 (migration cadence): m picked 'direct replace at T1, no flag' over the recommended flag-gated dev. §9 rewritten end-to-end: T1 ships the minimum-viable tracker visibly to users, replacing the catalog UI in the same PR. T2-T4 layer zoom + Akte + polish. T5 is cleanup-only.
The 5 on-rec picks: inline checkbox forks (Q1), sibling-collapse zoom (Q2), 6 curated defaults on cold open (Q4), latest-done-deadline Akte anchor (Q5), global Stichtag (Q6) — all locked as drafted in §1-§8.
Ready for review. Coder gate held; head decides T1 hire.
m's reframe (2026-05-27 20:43): /tools/procedures should be a workflow
tracker, not a catalog browser. Pick any procedural event, see backward
(predecessors) + self (where I am) + forward (successors), with
scenario_flags as togglable predicates and alternative constellations
explorable.
This shift-1 doc covers:
- 4-tab UX redo (single-pane radio-revealed entry form to fix the
pre-form-leak bug)
- Anchor visualisation (vertical waterfall with anchor at centre line)
- Three views — Anchor / Verfahren / Konstellationen — toggle preserves
anchor + scenario state
- Forward walk (current constellation only by default, conditional
reveal toggle, view-mode toggle reused from atlas P3)
- Backward walk (3 hops default, Akte mode overlays paliad.deadlines
actuals onto template chain)
- Compound rules drawer (per-anchor Querverweise affordance — column
shape owned by curie editorial workstream)
- Constellation viewer (inline per-flag preview drawer + full
Constellation view for browse)
- Akte entry (anchor derives from latest completed deadline)
- Migration: T1-T5 flag-gated dev under ?tracker=1, then hard-cut
Coder gate held. 11 open questions for m staged for AskUserQuestion in
4+4+3 batches. Decisions append as §13 before the
TRACKER DESIGN READY FOR REVIEW signal.
Per m's Q11 divergence in the design (no 2-week dual-ship), this slice
flips /tools/fristenrechner and /tools/verfahrensablauf to permanent 301
redirects to /tools/procedures and deletes the legacy frontend pages.
Bookmarks resolve via Location preservation of query params; no
?legacy=1 escape, no in-product affordance pointed back at the retired
URLs after the merge.
Server:
- handleFristenrechnerPage + handleVerfahrensablaufPage now 301 to
/tools/procedures, carrying any query string through unchanged.
- pillDrillURL in deadline_search_service.go retargets to
/tools/procedures so freshly indexed search pills land on the new
page directly (cached snapshots still work via the 301).
Frontend:
- Deleted src/fristenrechner.tsx, src/verfahrensablauf.tsx,
src/client/fristenrechner.ts.
- src/client/verfahrensablauf.ts loses its DOMContentLoaded auto-boot
and the now-unused initI18n / initSidebar imports; procedures.ts is
the sole caller of initVerfahrensablauf().
- frontend/build.ts drops the legacy entrypoints and renderXxx HTML
outputs.
- Sidebar.tsx, Header.tsx, index.tsx, paliadin-context.ts repointed
to /tools/procedures.
- Unused nav.fristenrechner / nav.verfahrensablauf /
tools.verfahrensablauf.* i18n keys removed.
Tests:
- verfahrensablauf_test.go rewritten to assert both legacy URLs return
301 with the correct Location (query string preserved).
Mounts the full Verfahrensablauf wizard — proceeding picker, perspective
chooser, date inputs, scenario flag rows, detail-mode toggle, view
toggle, timeline-container — under the /tools/procedures "Verfahren
wählen" tab. Per-rule scenario_flags chips (P0 SSoT) and the
Aufnehmen/Entfernen affordances reach the unified page unchanged since
they're delegated handlers on the timeline-container.
Refactor steps:
- Extracted the wizard body markup into a shared TSX component
(components/VerfahrensablaufBody) used by both verfahrensablauf.tsx
(legacy) and procedures.tsx (unified). U4 will retire the legacy
page; the shared component lets U3 ship without code duplication.
- Lifted the verfahrensablauf.ts DOMContentLoaded body into
initVerfahrensablauf() and re-exported it. The legacy auto-boot
stays in place but skips itself when #procedures-panel-proceeding
is present, so the unified page imports the module without
double-init. procedures.ts calls initVerfahrensablauf() the first
time the proceeding tab activates, gated by a one-shot flag to
preserve module-local selectedType / lastResponse across tab
toggles.
m flagged 2026-05-27 20:26: archived rules (e.g. the 5 mig 152 Mängelbeseitigung clones) clutter the /admin/procedural-events default view. They were correctly archived by mig 152 but visually noisy alongside active rules.
Fix: default activeLifecycle = 'published'. The 'Alle' chip still exists for when the user wants to see drafts + archived; 'Archived' chip surfaces them on demand. Initial view shows only the active corpus.
Mounts mountWizard() into #procedures-panel-wizard when the Geführt tab
activates. Same 5-row wizard, same backend (event search + follow-ups
probe) as the legacy /tools/fristenrechner. On R4 launchResult, the
wizard hands off to mountResultView which renders into the same
overhaul-root inside the panel.
The wizard renders into #fristen-overhaul-mode-host while Mode A and
the result view write into #fristen-overhaul-root. To keep those IDs
unique in the DOM — both modes look up via document.getElementById —
the host scaffold is no longer static on the search panel. The new
installOverhaulHost() helper tears down any existing host and installs
a fresh one inside the active tab's panel before each mount, so two
parallel hosts can't cross-wire when the user toggles between the
Direkt-suchen and Geführt tabs.
The U1/U2 placeholders are dropped from the panel markup since the
panels are populated dynamically now.
Mounts mountModeA() into #procedures-panel-search when the Direkt-suchen
tab activates. The legacy fristenrechner-mode-a code runs unchanged
inside a wrapper that reseeds the #fristen-overhaul-root /
#fristen-overhaul-mode-host scaffold on every tab activation, so
re-clicking the tab always restores a fresh Mode A surface even if the
previous interaction committed an event into the result view.
`?event=<code>` deep links still resolve: boot detects the param,
activates the search tab, and hands directly to mountResultView() —
the result lands inside the same root, the user sees the picked
event's follow-up rules with the Direkt-suchen tab as the visible
context.
Search-box-in-filter-strip composition with chip filters (m's Q3
divergence) lands later, after Mode B + Verfahrensablauf are folded —
the unified state machine pulls all three behind one search input.
First slice of the unified procedural-events tool train. Ships only the
page chrome — route, sidebar/header, filter strip with search box, four
entry-mode tabs (Verfahren wählen / Direkt suchen / Geführt / Aus Akte),
and the host containers later slices mount their UI into. No data wiring.
Per m's decisions (design §11.5): URL is English (/tools/procedures, not
/tools/verfahren); all four tabs visible from boot (not a single-default
landing); search box lives in the top filter strip and will compose with
chip filters once U1+ wire them.
U1 fills #procedures-panel-search (Mode A), U2 fills -wizard (Mode B),
U3 fills -proceeding + #procedures-output-tree (Verfahrensablauf), U4
hard-cuts /tools/fristenrechner and /tools/verfahrensablauf to 301
redirects and drops the legacy pages.
- audit of 6 surfaces with question→dimension matrix
- proposal: fold Fristenrechner + Verfahrensablauf into /tools/verfahren
- 4 entry paths converge on tree + linear output shapes
- mobile narrow-viewport rules + 3 worked personas
- 5-slice migration train (U0-U4), no DB migration
- 12 open questions in 3 batches for AskUserQuestion
Slice B.6 / S6 renamed the canonical edit URL from /admin/rules/{id}/edit
to /admin/procedural-events/{id}/edit. The backend handler + 301 redirect
landed, but the client-side regex in admin-rules-edit.ts:110 was missed —
it still only matches the legacy /admin/rules/.../edit shape. Result:
visiting the canonical URL from the list page shows 'Ungültige
Verfahrensschritt-ID in der URL.' even though the rule exists.
Fix: regex accepts both '/admin/procedural-events/{id}/edit' (canonical)
and '/admin/rules/{id}/edit' (legacy, kept for stale tabs / bookmarks
during the deprecation window).
m flagged 2026-05-27 17:57 on rule cc439590 (RoP.262.2, upc.inf.cfi).
ritchie shipped the final two slices of the Phase 2 train.
P2 — condition_expr write-validator:
- New internal/services/condition_expr_validator.go (136 LoC) — locks the grammar to {flag:<str>} OR {op:'and'|'or', args:[<leaf>|<composite>]} per design §4.1
- RuleEditorService.Create + Update reject non-conforming expressions
- 166-LoC test coverage; all 18 existing condition_expr rows validate
P4 (partial) — trigger_events deprecation (mig 156):
- NULLs out the 2 hybrid rules' trigger_event_id (parent_id is the canonical edge per §2.1)
- Adds 'Deprecated: see m/paliad#149' header on the legacy /api/tools/event-deadlines route
- Does NOT drop paliad.trigger_events nor the 5 read sites — those are gated on the editorial reparenting of the 73 orphan globals (NULL proceeding_type_id, served only via trigger_event_id). Editorial work is m's, not coder scope.
Comment on m/paliad#149 (issuecomment-10436) enumerates the exact next steps for the eventual follow-up coder once editorial reparenting completes.
Phase 2 train: P0 + S1+S1a + P1 + P3 + P2 + P4 partial — ALL shipped. Final P4 step waits on editorial.
Phase 2 P4 partial-scope (head approved 2026-05-27 15:24). The full
drop of paliad.trigger_events + the legacy route + 5 read sites is
gated on an editorial backfill that's not in coder scope — 73 active
sequencing_rules carry proceeding_type_id IS NULL and are addressed
ONLY via trigger_event_id today. Dropping anything would break those
73 orphans.
What this lands:
1. Mig 156 — NULL out trigger_event_id on the 2 hybrid rules that
carry BOTH parent_id AND trigger_event_id. Per design §2.1 /
m's Q1, parent_id is the canonical predecessor link; the
hybrid trigger_event_id was redundant. The 2 rules' parent_id
chains keep the live edge. Live-DB verified post-apply: 0
active hybrid rules remain.
2. Deprecation + Link headers on POST /api/tools/event-deadlines
per RFC 8594 / RFC 9745. The route stays functional so the 73
orphans keep working until reparenting lands.
What this does NOT land (gated on editorial):
- DROP TABLE paliad.trigger_events
- DROP COLUMN paliad.sequencing_rules.trigger_event_id
- Remove the legacy /api/tools/event-deadlines handler
- Remove EventDeadlineService + ExportService::1680 sheet
- Remove deadline_rule_service.go:226 label-fallback path
- Remove event_type_service.go:40+414 reads (33 event_types still
reference trigger_event_id)
- Update cmd/gen-upc-snapshot/main.go:185-202 to skip trigger_events
- Drop the sequencing_rules_trigger_event_id_fkey FK
All of the above lands in a follow-up mig once the orphan count
hits zero. Comment to follow on m/paliad#149 with the editorial-
backlog list.
Verified: live-DB pre/post hybrid count (0 active hybrids remain);
mig idempotent; go vet clean.
Design: docs/design-deadline-system-revision-2026-05-27.md §2.1
(parent_id canonical), §3.4 (legacy route fate), §4.3 (table fate),
§5 (slice train P5 row). t-paliad-331.
Phase 2 P2 (design §4.1). Locks the condition_expr grammar to:
CondExpr := { "flag": "<known_flag>" }
| { "op": "and"|"or", "args": [<CondExpr>, ...] }
Where <known_flag> must exist in paliad.scenario_flag_catalog (today:
with_ccr / with_amend / with_cci; editorial adds via the catalog
table as needed).
Wire-time validation in RuleEditorService.Create and UpdateDraft —
the rule editor surfaces a 400 with a friendly message before the row
hits the DB. Empty / JSON null inputs pass through (the "no gate"
shape; stored as NULL column).
The validator:
* walks the JSON tree once, collecting every leaf flag name
* rejects mutually-exclusive shapes (leaf + composite in one node)
* rejects empty args, bad op values, empty flag strings
* does ONE batch lookup of the collected leaf names against the
catalog (regardless of expression depth)
Tests:
* 9 shape-only unit tests covering every reject path (no DB needed)
* TestValidateConditionExpr_LiveCatalog covers 6 good shapes + 2
unknown-flag cases against the live catalog
* TestConditionExpr_AllLiveRowsValidate runs the validator over
every active+published condition_expr in paliad.sequencing_rules
to enforce the §4.1 invariant on every deploy (today's 18 rows
all conform — verified via Supabase MCP pre-flight)
Live-DB tests skip cleanly when TEST_DATABASE_URL is unset (same
posture as sibling live tests in this package).
Design: docs/design-deadline-system-revision-2026-05-27.md §4.1
(grammar formalisation). t-paliad-331.
ritchie shipped m's headline UX (paliadin priority signal 14:58):
'The new timeline filters for optional / mandatory / show only selected
is what I am most waiting for. I want this to be consolidated for all
our deadlines so we can simulate all proceedings.'
Three-way detail-level filter above the Verfahrensablauf result panel:
- Nur Pflicht — only priority='mandatory' rules
- Gewählt (default) — mandatory + recommended + every explicit per-rule override in projects.scenario_flags
- Alle Optionen — every rule, unselected ones rendered dotted-border + muted
State persists per-user via localStorage['verfahrensablauf:view_mode']. Per-rule Aufnehmen/Entfernen chips wire to projects.scenario_flags via the P0 SSoT (rule:<uuid> entries).
New files: verfahrensablauf-detail-mode.ts (125), verfahrensablauf-detail-mode.test.ts (96), filter wiring in verfahrensablauf.ts (+204) and views/verfahrensablauf-core.ts (+37). 63 LoC CSS (dotted-border treatment).
bun build clean, 264 frontend tests pass (8 new), go vet clean.
Ritchie continuing with P2 (condition_expr write-validator) then P4 (legacy deprecation).
m's headline UX ask (2026-05-27 14:58, paliadin priority signal):
"The new timeline filters for optional / mandatory / show only
selected is what I am most waiting for. I want this to be
consolidated for all our deadlines so we can simulate all
proceedings."
Phase 2 P3. Adds a three-way detail-level filter above the result
panel on /tools/verfahrensablauf:
( ) Nur Pflicht — only priority='mandatory' rules
(•) Gewählt — mandatory + recommended (default) + every
explicit per-rule override the user has set
in projects.scenario_flags
( ) Alle Optionen — every rule, with unselected ones rendered
dotted-border + muted so the user sees what
they're NOT considering
State persists per-user via localStorage["verfahrensablauf:view_mode"].
The filter is pure client-side narrowing on the calc payload — flipping
the toggle re-renders instantly without a fresh backend call.
Per-rule selection (design §2.4a): every optional / recommended card
now carries an [Aufnehmen] / [Entfernen] chip. Clicking writes a
"rule:<uuid>" entry into the project's scenario_flags via the P0 SSoT
PATCH endpoint, recording only deviations from the priority default:
recommended + entfernen → rule:<uuid> = false (explicit deselect)
optional + aufnehmen → rule:<uuid> = true (explicit select)
flipping back to the default deletes the entry
Mandatory rules never expose the chip — they cannot be deselected.
Wire-shape change: CalculatedDeadline gains `ruleId` (the backend already
emits it as `ruleId` in TimelineEntry; only the frontend interface needed
to surface it).
Conditional handling: a conditional rule whose predicate doesn't fire
is treated as unselected in "Gewählt" mode (even when priority=
mandatory) — mandatory means "must be filed IF the predicate fires",
not "always render". The "Alle Optionen" view re-surfaces it so the
lawyer can see what scenario would unlock it.
Cross-surface coherence: hydrating ?project=<id> reads scenario_flags
from the SSoT and pre-fills the existing flag checkboxes (with_ccr /
with_amend / with_cci) so the page reflects the project's persisted
state on first paint. Every flag toggle + every per-rule chip click
PATCHes back. The page also listens for the scenario-flag-changed
CustomEvent fired by peer surfaces (Mode B Fristenrechner result-view)
and re-renders without a fresh fetch.
i18n: 5 new keys (deadlines.detail.{label,mandatory_only,selected,
all_options,optional_unselected_hint,aufnehmen,entfernen}) DE + EN.
CSS: dotted-border + muted treatment on .timeline-item-header--
unselected; .timeline-selection-chip with --add (lime accent) and
--remove (discreet muted) variants.
Tests: 8 new unit tests covering isRuleSelected (4 priority × 2 flag
state matrix) and filterByDetailMode (3 modes × default/override cases).
Verified: bun build clean, bun test 264/264 (8 new), go vet clean.
Design: docs/design-deadline-system-revision-2026-05-27.md §2.4a
(selection state model), §3.3a (view-mode toggle), §6 (Entry A spec).
t-paliad-331. Re-prioritised by m via paliadin 14:58.
Phase 2 P1 / m's Q5 divergence (2026-05-27, verbatim):
"Reverse the unification as suggested in 3. They are different
proceedings, I only wanted the approach to be unified in the
'determinator' — but they are actually different proceedings!"
Mig 155 reverts the mig-096 unification:
Before: id=160 upc.apl.unified active (16 rules), id=11/19/20 inactive
After: id=11 upc.apl.merits (7 rules), id=19 upc.apl.cost (2 rules),
id=20 upc.apl.order (7 rules) all active; id=160 inactive
The 16 rules under id=160 split cleanly by event_code prefix; all 10
parent_id edges among them are bucket-local (pre-flight audit), so
the tree shape survives the rebind unchanged.
Spawn FK retarget: pi.cfi.appeal_spawn flips from 11 (merits) → 20
(orders track) per design §3.1 — PI appeals land on orders, not
merits. The inf/rev/dmgs spawns keep target=11 (merits), now active.
Determinator routing layer (proceeding_mapping.go) keeps its single
"Berufung" front door per m's intent — only the data shape changes.
Pre-flight verified: 0 projects bound to id=160, 0 scenarios reference
upc.apl. Zero data migration on the project side.
Tests: lookup_events_test.go assertions on the three appeal_target
buckets updated to the new codes (endentscheidung → upc.apl.merits,
schadensbemessung → upc.apl.merits, bucheinsicht → upc.apl.order).
Same rule set, post-split coordinates.
Snapshot regen (pkg/litigationplanner/embedded/upc/) deferred: the
current snapshot only contains inf+rev so the apl re-split doesn't
shift its contents; regenerating would surface unrelated active PTs
and pollute this slice. Tracked as a follow-up.
Verified: go vet clean, go test ./internal/services/... -run
LookupEvents|proceeding_codes clean.
Design: docs/design-deadline-system-revision-2026-05-27.md §3.1
(re-split mig), §1.3 (spawn graph post-Q5). t-paliad-331.
Phase 2 S1 + S1a (pre-ratified from t-paliad-327, folded into the
Phase 2 train).
S1 — Cross-party display:
- FristenrechnerService.LookupFollowUps stops filtering by party
server-side; queryFollowUpRows drops the perspective WHERE clause
and returns every published+active child.
- Server now computes is_cross_party per row (true only when
perspective ∈ {claimant,defendant} AND primary_party is the
opposite side; NULL/both/court is never cross-party).
- FollowUpRule wire shape gains the boolean.
- Frontend renderRule adds a "Gegenseitig" badge + is-cross-party
row class (muted styling, disabled checkbox affordance).
- defaultChecked returns false for cross-party rows.
- countSelected + submitWriteBack skip cross-party rows
unconditionally — even if a user manually checks the box, they
describe opposing-side filings and don't belong in our Akte set
(design §2.4 write-back exclusion).
- i18n: deadlines.overhaul.crossparty.badge / .tooltip (DE+EN).
- CSS: .fristen-overhaul-rule-crossparty + .is-cross-party row
modifier.
S1a — Spawn-only picker filter:
- SearchEvents WHERE now adds `sr.is_spawn = false` so spawn rules
(e.g. appeal_spawn, the inf.cfi → upc.apl.merits hop) no longer
surface as picker hits. Spawn rules are consequences, not
triggers — a lawyer searching "Berufung" wants the appeal-tree
root, not the inf.cfi spawn link.
- Terminal leaves (Duplik etc.) stay pickable per design §2.2's
carve-out: their own anchor is non-spawn, so they surface and
render an honest empty follow-up list.
Honest UX: hiding cross-party follow-ups lied about what the
workflow does next (cf. RoP.029.d falling off when perspective=
claimant on def_to_ccr — the workflow continues, just on the
defendant's docket). The fix makes the data legible without
contaminating the write-back path.
Verified: go vet clean, bun build clean, bun test 256/256,
go test ./internal/services/... -run LookupFollowUps... clean.
Design: docs/design-deadline-system-revision-2026-05-27.md §2.4
(cross-party) + §2.2 (spawn-only picker). t-paliad-331.
brunel aligned the embedded snapshot calendar with paliad-side policy from t-paliad-121: UPC vacation rows stay in the data for informational annotation but no longer trigger IsNonWorkingDay → AdjustForNonWorkingDays leaves dates intact.
Changes:
- pkg/litigationplanner/embedded/upc/holidays.go: IsNonWorkingDay now returns true only on closure (not vacation). Vacations still surface via AdjustForNonWorkingDaysWithReason for labelling.
- pkg/litigationplanner/embedded/upc/holidays.json: regenerated from live paliad.holidays. Was 5 placeholders → now 55 holidays (33 vacation + 22 closures). Includes UPC Winter Vacation rows.
- pkg/litigationplanner/embedded/upc/meta.json: snapshot version bump.
- snapshot_test.go: +42 lines covering the vacation non-blocking behavior with regression cases.
Affects youpc.org/deadlines (consumes pkg/litigationplanner via Go module replace) — picks up automatically on rebuild.
youpc.org/deadlines was rolling a deadline "from 2027-01-02 (UPC Winter
Vacation)" — i.e. across the UPC judicial vacation as if it were a
public holiday. Paliad-side t-paliad-121 already decided vacations are
informational only (the Court keeps running through them, RoP / UPC AC
decision-on-judicial-vacation 2023-05-26), and `HolidayService.Is
NonWorkingDay` in `internal/services/holidays.go` is correct. The
embedded snapshot consumed by youpc.org via Go-module replace had
drifted: `pkg/litigationplanner/embedded/upc/holidays.go:74` blocked on
both `isClosure()` AND `isVacation()`.
This commit aligns the embedded calendar with the paliad-side semantics
and ships a fresh holiday set so the existing 2026/2027 fix actually
takes effect downstream.
Code changes (`holidays.go`):
- `IsNonWorkingDay`: drop the `|| h.isVacation()` branch — only weekends
and `isClosure()` rows trigger the roll. Godoc rewritten to mirror
the paliad-side rationale (Court keeps operating, RoP cites,
vacation rows kept for informational labels).
- `isClosure()`: accept both `"public_holiday"` and `"closure"`. Live
paliad DB rows use the `public_holiday` value; the placeholder
snapshot shipped with the original Slice C used `closure` as a
hand-crafted synonym. Reconciles with
`internal/services/holidays.go:132` which already does the same
union. Required to make the regenerated JSON (full of
`public_holiday`) keep blocking DE national holidays after the
regeneration in this commit.
- Type-level godoc updated: `SnapshotHolidayCalendar` now documents
vacation-is-informational, and the `AdjustForNonWorkingDaysWithReason`
precedence note explains that `vacation` kind only fires when a
vacation row overlaps a weekend or closure that's already doing the
rolling.
Data refresh (`holidays.json`):
- Regenerated from paliad prod (postgres @ 100.99.98.201:11833,
paliad schema). 55 rows for 2026 + 2027: 22 DE public_holiday +
33 UPC vacation (25 Summer Vacation Jul 27–Aug 28, 8 Winter
Vacation Dec 24/28–31 + Jan 4–6). The previous placeholder shipped
only 5 rows (3 Sommerpause + Neujahr + Tag der Arbeit, no Winter
Vacation at all) — which is why a date landing in late Dec / early
Jan landed inside an unmodeled gap on the consumer side.
- `meta.json` bumped: version → `2026-05-27-1-holidays-only`,
`holiday_count` 5 → 55, `source_db_label` flags that only
holidays.json was refreshed (see friction note below).
Regression test (`snapshot_test.go::TestSnapshotHolidayCalendar`):
- 2026-08-04 (Tue, UPC Summer Vacation) — `IsNonWorkingDay` must be
false; `AdjustForNonWorkingDays` must NOT mutate the date.
- 2027-01-02 (Sat, m's flagged scenario) — must roll forward through
Sat/Sun, then STOP on Mon 2027-01-04 (UPC Winter Vacation, no longer
blocking). Pre-fix this rolled all the way to Thu 2027-01-07.
Cross-repo: youpc.org imports `pkg/litigationplanner` via Go-module
replace; the regenerated snapshot ships on its next rebuild. No
separate youpc.org commit needed — paliad is the source of truth.
Friction note: `cmd/gen-upc-snapshot/main.go` itself is incompatible
with the current paliad schema. Migration 140 (`140_drop_deadline_rules`)
dropped `paliad.deadline_rules`, but the generator still SELECTs from
it (main.go ~L162). Running the tool against prod fails on the rules
step. I bypassed the broken path and generated `holidays.json` directly
from the DB via psql + jq (same JSON shape that `EmbeddedHoliday`
expects, nulls filtered for `omitempty`). The other snapshot files
(rules.json, proceeding_types.json, trigger_events.json, courts.json)
remain at their pre-existing placeholder state — re-flagged in
meta.json's `source_db_label`. Refitting the generator for the post-
mig-140 schema is a separate task.
go vet + go test ./... clean (256+ Go tests pass, including the new
regression cases).
Phase 2 P0 of the deadline + procedural-events revision. Establishes
paliad.projects.scenario_flags (jsonb) + paliad.scenario_flag_catalog as
the single source of truth for per-project scenario state — replacing
the three fragmented stores athena flagged (project_event_choices,
scenarios.spec, DOM-only). All three were empty per the audit so no
data migration is needed.
The jsonb map carries two key shapes:
* named flags (whitelist via scenario_flag_catalog) — today
with_ccr / with_amend / with_cci
* per-rule selection deviations of shape "rule:<uuid>" — wired up
here for validation; the consumer UI lands in P3
Endpoints:
GET /api/projects/{id}/scenario-flags
PATCH /api/projects/{id}/scenario-flags
PATCH semantics: bool = write; null = delete (priority-driven default
returns); missing key = leave alone. The service validates every key
on write (catalog lookup + UUID rule-membership + mandatory-cannot-be-
deselected) before persisting, so a single bad key fails the whole
patch.
Frontend bind: new scenario-flags.ts client module + Mode B's flag
checkboxes (ccr-flag / inf-amend-flag / rev-amend-flag / rev-cci-flag)
now hydrate from / persist to the project's scenario_flags on every
toggle. Kontextfrei (no project) is unchanged. Cross-surface coherence
via a scenario-flag-changed CustomEvent (peer surfaces — Verfahrens-
ablauf strip, Mode B result-view — will subscribe in P3).
Mig 154 is audit-defensive (set_config of paliad.audit_reason); no
audit trigger fires on paliad.projects today but a future one will
inherit the reason. Seeds the three known flags. CHECK constraints
enforce the top-level shape (jsonb_typeof = 'object') and the
catalog key pattern (lowercase, not 'rule:%' prefix).
Verified against the live DB: 18 projects default to '{}', catalog
has 3 rows, applied_migrations advanced to 154.
Design: docs/design-deadline-system-revision-2026-05-27.md §2.3, §2.4a,
§4.1, §5 (P0 row). t-paliad-331.
brunel converted hardcoded hex to existing design tokens for the .timeline-* classes (Verfahrensablauf at ?view=timeline). No new tokens introduced; 11 existing tokens reused. Layout/spacing untouched.
Mirrors the t-paliad-326 playbook applied earlier to the Fristenrechner overhaul CSS, but on the separate timeline view that wasn't in scope then.
The /tools/verfahrensablauf?view=timeline page (and the columns-view
mirror via `.fr-col-item--*` modifiers) had hardcoded light backgrounds
that survived a dark-mode flip. Root cause: four undefined custom
properties (`--color-bg-soft`, `--color-border-soft`, `--color-accent-bg`,
`--brand-lime`) consumed by the `.timeline-*` and adjacent
`.event-card-choices-*` rules, each with a hardcoded hex / RGBA fallback.
Since neither :root nor :root[data-theme='dark'] defines those tokens,
every consumer fell through to the light fallback in both themes —
leaving the conditional-row bg, the popover surface, the option-button
bg, the chip-skipped bg, and the popover block-separator border stuck on
near-white in dark mode. Earlier t-paliad-326 covered the new
Fristenrechner overhaul CSS only; the timeline view styles are a
separate block (~L3337-3810 in `frontend/src/styles/global.css`) and
were not touched.
Migrate every consumer to the existing dual-theme tokens already used
across paliad. Same approach m approved for t-paliad-326, no new tokens
introduced (all reuse):
- Card / popover surfaces (`.event-card-choices-popover`,
`.event-card-choices-option`) → `--color-surface` (white light /
card-tint dark) so the popover reads as raised above the body in
both themes.
- Subtle raised surface for conditional row, skipped chip, option
hover (`.timeline-item--conditional .timeline-content`,
`.fr-col-item--conditional`, `.event-card-choices-chip-part--skipped`,
`.event-card-choices-option:hover/:focus-visible`) →
`--color-surface-muted`. **This is the visible bug fix m flagged.**
- Lime-tint backdrops (`.timeline-context-note` bg,
`.event-card-choices-caret:hover/:focus-visible`) →
`--color-bg-lime-tint` (lime-alpha 0.10 light / 0.12 dark).
- Active-option chip bg (`.event-card-choices-chip-part`) →
`--color-accent-soft-bg`.
- Lime accent borders / fills (`.timeline-context-note` left border,
`.timeline-date--overridden`, `.frist-date-edit-input`,
`.event-card-choices-unhide-btn`, `.event-card-choices-option--active`)
→ `--color-accent` / `--color-accent-fg`. Drops the
legacy `#c6f41c` fallback that doesn't match the current brand
(`--hlc-lime: #BFF355`).
- Neutral borders (`.event-card-choices-caret`,
`.timeline-item--hidden .timeline-content`, `.fr-col-item--hidden`,
`.timeline-item--conditional .timeline-content`,
`.event-card-choices-popover`, `.event-card-choices-option`,
`.event-card-choices-block + .event-card-choices-block`) →
`--color-border` (warm cream-derived light / cream-alpha dark).
- Popover shadow `rgba(0, 0, 0, 0.12)` → `var(--shadow-md)`
(auto-deepens to `rgba(0, 0, 0, 0.45)` in dark).
- Status red (`.event-card-choices-error`) → `--status-red-fg`
(defined in both themes; old `#b00020` fallback unreachable).
Zero new tokens introduced — every replacement uses a token already
shipped in both :root and :root[data-theme='dark']. Verified by
mounting `frontend/dist/assets/global.css` against a representative
static DOM (context-note banner, every timeline-item variant —
conditional / skipped / hidden / overridden-date / mandatory —
popover with active/inactive options, unhide button, error message,
all four party-badge stances) and toggling `data-theme` between
light and dark: conditional row bg flips from grey to muted-cream-on-
midnight, popover lifts off the body via `--shadow-md`, every chip and
border stays legible in both themes.
bun build + bun test (256 pass) + go vet clean.
m's clarification at 14:40 reframed the original "rarity" framing: every
optional rule is a per-scenario selectable card; the Verfahrensablauf
gets a three-way detail-level filter (Nur Pflicht / Gewählt / Alle
Optionen). The CCR-dropdown pattern generalises to per-rule chips.
Three folded additions:
§2.4a — Selection state + detail-level filter. NO new column on
sequencing_rules (reverts the earlier is_edge_case strawman). Extends
projects.scenario_flags jsonb to carry both named flags (with_ccr etc.)
and per-rule entries (rule:<uuid>). Storage only carries deviations
from the priority default (recommended = default-selected,
optional = default-unselected). Whitelist accepts rule:<uuid> when the
UUID resolves to an active rule on the project's PT.
§3.3a — Verfahrensablauf view-mode toggle: three-way segmented control,
localStorage persistence, default "Gewählt". Mode B result view stays
single-purpose (no view-mode toggle there).
§4.2.1 — R.109 translation chain editorial worked example: R.109.1 stays
as optional anchor; R.109.4 reparents to R.109.1 with condition_expr
{flag: with_interpreter_denied} and primary_party=both (parties, not
court); R.109.5 reparents to R.109.1 with {flag: with_translation_granted}.
Introduces two new flags to scenario_flag_catalog.
§6 UI spec updated: two mocked tree states (Gewählt + Alle Optionen)
showing the dotted-border [Aufnehmen] chips, [Entfernen] on selected
optionals, greyed-with-hint on flag-gated conditionals, and the
subtree-hide-on-unselected-ancestor render logic.
§10.0a captures the additions; §10.1 notes they don't change the slice
train (P0 + P3 take the extended scope; no new mig).
Builds on athena's Phase 1 assessment (9aee9e4) + atlas's t-paliad-327
pre-ratified subset. m's Option B direction: "overall schema for all
procedural events and how they are connected" — connection graph as the
spine.
Connection schema (§1):
- Rules are nodes, parent_id is the canonical edge, spawn rules are the
cross-PT edges, condition_expr filters the visible subgraph
- ASCII trees for the 3 largest PTs (upc.inf.cfi 25, upc.rev.cfi 17,
upc.apl post-Q5-split 16); Mermaid graph for the 4 spawn cross-PT edges
- Per-PT health table covering all 23 active primaries (17 ruled + 6 empty)
m's 12 design decisions (3 batches of 4 via AskUserQuestion):
Tier 1 — model (all 4 on-recommendation):
- Q1: parent_id is canonical, deprecate trigger_event_id
- Q2: Reparent 73 legacy globals via editorial walk
- Q3: Derive trigger discoverability from data (EXISTS)
- Q4: projects.scenario_flags jsonb (confirms t-paliad-327 design)
Tier 2 — surface (1 divergent, 3 on-recommendation):
- Q5 DIVERGENT: Reverse the upc.apl unification — split back into 3 PTs
(merits/cost/order). m: "I only wanted the approach to be unified in
the 'determinator' — but they are actually different proceedings!"
Mig P1 retargets 16 rules by event_code prefix.
- Q6: Show empty PTs with "Keine Regeln gepflegt" badge
- Q7: Fold Entry A into /tools/verfahrensablauf
- Q8: Drop /event-deadlines after 73-globals reparenting
Tier 3 — editorial (all on-recommendation):
- Q9: Lock condition_expr grammar {flag} | {op:and|or, args}
- Q10: parent-NULL filter on /admin/procedural-events
- Q11: Drop trigger_events table once route is gone
- Q12: ASCII per-PT + Mermaid spawn graph
6-slice migration train (§5): P0 scenario SSoT, P1 appeal re-split, S1/S1a
from t-paliad-327, P2 empty-PT badge, P3 Entry A, P4 editorial walk, P5
trigger_event_id deprecation. P5 gated on P4.
No code yet — coder gate held per inventor SKILL.
t-paliad-328. Read-only audit of every consumer of paliad.sequencing_rules
+ paliad.procedural_events + the legacy paliad.trigger_events, plus the
rules-corpus quality on the live database. No design — Phase 2 (inventor)
gates on this landing.
Highlights:
- 226 active+published rules / 222 events (1:1 since mig 136)
- parent_id chain vs trigger_event_id are functionally disjoint
(2/226 overlap); 73 legacy globals own the trigger_event_id lane
- 11 risk items captured with file:line; B1 (cross-party follow-up
filter) and B2 (picker accepts spawn-only + leaves) confirmed
from code at fristenrechner_followups.go:358-367 and :241-287
- 4 spawn rules still point at the inactive upc.apl.merits (id=11);
the active appeal type is id=160 (upc.apl.unified)
- 6 active proceeding_types are entirely unruled
- 3 scenario stores wired (project_event_choices, scenarios table,
DOM state); all currently empty, so divergence is dormant
- 738 lines (under the 800 cap)
Recommendation §6 sequences Tier 1 model decisions ahead of Tier 2
surface decisions and Tier 3 editorial cleanup for the inventor.
brunel fixed m's bug ('Das CSS vom neuen Fristenrechner scheint wieder keinen Darkmode zu supporten') by migrating the 121 hardcoded hex colors knuth added in S2/S3/S4 to the project's design-token system.
Net: 161 inserts / 123 deletes in frontend/src/styles/global.css. 10 new tokens added to :root and :root[data-theme='dark'] for the few shades that didn't have an existing variable (group dividers, party-stance backgrounds, filter-pill subtle states). All 121 hex usages replaced with var(--color-*) references.
Verified visually via standalone harness: trigger card, 4 priority groups, per-rule rows (claimant/defendant/both/court), Mode A filter strip + result list, Mode B wizard with Filter/Qualifier badges, kontextfrei nudge, write-back footer, success/error toasts — all flip cleanly between light and dark. Layout/spacing/sizing untouched.
bun build + go vet clean.
The Fristenrechner overhaul CSS shipped in S2/S3/S4 (commits 9ab8dd8,
2a2c5b8, 70985d8) used hardcoded hex literals across the result view,
Mode A search, and Mode B wizard surfaces. The `:root[data-theme="dark"]`
flip had nothing to override, so toggling the theme left the whole
Fristenrechner pane stuck in light-mode colors.
Migrate every hex literal in those sections to the design-token system
that the rest of paliad already uses (PWAHead.tsx flips
`data-theme` from localStorage):
- Surfaces: `#fff`/`#fafaf6`/`#f4f4f0` → `--color-surface` /
`--color-surface-2` / `--color-bg-subtle`.
- Borders: `#d8d8cf`/`#e0e0d4`/`#ececde` → `--color-border`;
`#c8c8be`/`#d4d4c9`/`#d4d4cc` → `--color-border-strong`.
- Text: `#1f1f1f`/`#2a2a2a` → `--color-text`; `#444`/`#555`/`#666` →
`--color-text-muted`; `#777`/`#888`/`#999` → `--color-text-subtle`.
- Status palette: error → `--status-red-*`; spawn/cond badges +
court-set hint → `--status-amber-*`; ok-msg → `--status-green-*`;
claimant party + filter-row badge → `--status-blue-*`; recommended
group stripe → new `--status-blue-border`; conditional stripe →
`--status-amber-border`.
- Defendant/court party stances → `--status-red-*` /
new `--status-purple-*` bucket.
- Brand-lime cues (mandatory group stripe, mode-tab active underline,
wizard row-number circle) → `--color-accent` / `--color-accent-dark`.
- Lime soft tints (nudge, footer, hover bgs, success message, "from
Akte" wizard row, edit-button hover) → new
`--color-accent-soft-{bg,fg,border}` tokens.
- Saturated lime pills (active chip, jurisdiction badge, wizard
active-row outline) → new `--color-accent-strong-{bg,fg,border}`
tokens.
- Lime accent links (rule-source, edit-date, result-cta, wizard-edit)
→ existing `--color-accent-fg` (midnight in light, lime in dark).
- Wizard active-row glow `rgba(198, 244, 28, 0.15)` → token-driven
`rgb(var(--hlc-lime-rgb) / 0.15)`.
- Trigger card box-shadow → `var(--shadow)` (auto-deepens in dark).
Ten new tokens introduced in `:root` + mirrored in
`:root[data-theme="dark"]`: 6 accent-soft/-strong, 1 status-blue
border, 3 status-purple bucket.
Verified by mounting `frontend/dist/assets/global.css` against a static
representative DOM (all four group stripes, every party stance, mode-A
filter + result list, mode-B wizard with filter/qualifier badges,
trigger card, write-back footer, kontextfrei nudge, ok/error
messages). Toggled `data-theme="dark"` via JS — every surface, border,
chip, badge, and status pill flipped to its dark counterpart.
`bun run build` + `go vet ./...` clean. Layout / spacing / sizing
untouched (colours, borders, shadows only).
NO CHANGES IN FUNCTIONALITY. PoC pane only flips visuals when the
theme is toggled now.
t-paliad-326.
knuth shipped S6, the final slice of the Fristenrechner overhaul:
- frontend/src/client/fristenrechner.ts shrinks by 137 LoC (legacy Pathway-B neutralised; row-stack subtree wired off behind ?legacy=1).
- internal/handlers/fristenrechner_event_categories.go dropped — the /api/tools/fristenrechner/event-categories endpoint is gone (route deregistered in handlers.go).
- paliad.event_categories table stays for future tools (the hidden 'Ich möchte einreichen' forward-workflow), per design §7-S6.
- Deferred follow-ups (knuth's scope discipline): drop the legacy concept-card response shape from /search + lift the dead-code row-stack subtree out of fristenrechner.ts in a separate cleanup PR. Filed as scope note on m/paliad#146 (issuecomment-10414).
S1-S6 complete:
- S1 7ea4151 — backend (search ?kind=events + /follow-ups)
- S2 9ab8dd8 — result view under ?overhaul=1
- S3 2a2c5b8 — Mode A direct search
- S4 70985d8 — Mode B 5-row wizard
- S5 4571bd4 — flip overhaul default
- S6 ba3e079 — cascade endpoint drop + legacy neutralise
Procedure-mode (upper half of fristenrechner.tsx) untouched per design. paliad.event_categories table retained for future tools.
Cleanup pass per design §7 / S6, executed as a measured first cut
that drops the cascade endpoint + neutralizes the legacy Pathway B
row-stack / cascade init without lifting the entire ~1500 LoC
subtree out of `fristenrechner.ts`. The dead helpers stay for one
follow-up that can lift them safely.
Backend:
* Deleted `internal/handlers/fristenrechner_event_categories.go`.
* Dropped the `GET /api/tools/fristenrechner/event-categories`
route from `handlers.go`. The `EventCategoryService` itself
stays — it still backs the legacy concept-card search's
`?event_category_slug=` filter, which dies in the same
follow-up that removes the concept-card response shape.
* `paliad.event_categories` TABLE is untouched per design §7
(kept for future tools).
Frontend:
* `loadEventCategoryTree()` reduced to a stub returning `[]` — the
endpoint it fetched no longer exists, and no overhaul surface
calls it.
* `initB1Cascade()`, `initForumFilter()`, `initInboxFilter()`
early-return. Their `DOMContentLoaded` registrations stay so
the bundle exports are stable, but no Pathway B cascade /
chip-strip / inbox-channel wiring fires in `?legacy=1` mode.
* The Pathway B markup in `fristenrechner.tsx` stays in place; it
renders inert when a user hits `?legacy=1&path=b`.
* `buildRowStack`, `renderRowStack`, `runB1Search`, and the row-
stack helper functions remain as unreachable code. Removing
them mechanically requires retiring the entire upper-half
Pathway B B2 search wiring (`runSearch` + `renderConceptCard`
+ `renderSearchResults` + `SearchResponse` types) which is
tangled with the legacy concept-card response shape — deferred
to a follow-up that lands together with the backend
concept-card removal.
Verified — bun build clean (2971 i18n keys unchanged), 256
frontend tests pass, go build + vet clean, live-DB tests
(TestListProceedings, TestSearchEvents, TestLookupFollowUps)
still green.
Follow-up scope tracked in design §7 S6 — pending the helper-tree
lift and the legacy concept-card response-shape removal from
/search.
knuth flipped the overhaul flag per design §7-S5:
- isOverhaulMode() inverted: true unless ?legacy=1.
- /tools/fristenrechner now lands on the new dual-mode (Direkt suchen + Geführt) by default.
- Legacy row stack still reachable via ?legacy=1 for the 2-week deprecation window.
- Existing ?overhaul=1 deep links continue to work (no-op pass-through).
- Sidebar / header / outbound URLs unchanged — they point at bare /tools/fristenrechner so they pick up the new default automatically.
S6 (drop buildRowStack + cascade reads) next on the same branch.
`isOverhaulMode()` now returns true unless the URL carries
`?legacy=1`. The overhaul UI from S2-S4 (mode tabs + Mode A
search + Mode B wizard + shared result view) becomes the default
landing for /tools/fristenrechner; the legacy three-step wizard +
Pathway A/B + cascade is reachable only via the explicit
`?legacy=1` opt-out for the two-week deprecation window before
S6 drops the legacy code paths entirely.
The pre-existing `?overhaul=1` deep links from S2-S4 still
resolve — the detector treats *absence* of `?legacy=1` as
overhaul, so bookmarks stay valid. No sidebar / header / outbound
link change needed: those all point at the bare
`/tools/fristenrechner` URL, which now boots overhaul.
Verified — bun build clean (2971 i18n keys unchanged), 256
frontend tests pass, go build + vet clean.