Adds paliad.countries (13 ISO-3166 codes), paliad.courts (41 entries
seeded from internal/handlers/courts.go), and the country/regime split
on paliad.holidays. The 33 t-paliad-121 UPC vacation rows previously
stored as country='UPC' migrate cleanly to country=NULL + regime='UPC'
— 'UPC' is a supranational regime, not an ISO country, and the new
shape lets a UPC LD München (country='DE', regime='UPC') pull both DE
federal holidays and UPC vacation entries while a UPC LD Paris
(country='FR', regime='UPC') pulls FR + UPC. Holidays now FK-protected
against typo'd country codes.
Archives m's locked design call (2026-05-05 18:51) plus live-codebase
verification: paliad.holidays.country exists per-country; paliad.courts
does not (must create); proceeding_types.jurisdiction is regime not
country (do not remove); 41 hand-curated courts already in
internal/handlers/courts.go ready to seed; HolidayService.loadYear is
country-blind today (latent bug); germanFederalHolidays merge is
hardcoded (must become country-conditional). Task stays ON-HOLD until a
non-DE forum or EPO closure-day calendar comes into scope.
The v3 result cards were dead-ends: clicking a Klageerwiderung pill
showed no deadline; users had to switch to Pathway A's wizard, retype
the date, and read the deadline out of the timeline. v4 makes the card
the entry to a single-rule calculator + add-to-project flow per m's
2026-05-05 11:58 feedback.
Backend (single-rule calc, no parent walk):
- New POST /api/tools/fristenrechner/calculate-rule endpoint accepts
either ruleId OR (proceedingCode + ruleLocalCode), trigger date, and
optional condition flags. Returns rule metadata + computed dueDate +
originalDate + adjustment-reason chip data.
- FristenrechnerService.CalculateRule() reuses the existing addDuration
+ HolidayService.AdjustForNonWorkingDaysWithReason pipeline so
t-paliad-119's adjustment-reason explainer and t-paliad-121's UPC-
Sommerferien skip both apply automatically. Court-determined rules
(party='court' or event_type ∈ hearing/decision/order) return
IsCourtSet=true and an empty due date.
- Flag-conditional rules surface FlagsRequired even when the caller
hasn't supplied the flag — the UI uses this to render checkboxes;
toggling recomputes live. With all flags satisfied + alt_duration_*
present, the calc swaps to alt values (existing semantics).
- Live-DB integration test covers plain calc, court-set, flag handling,
and error paths (skipped without TEST_DATABASE_URL).
Frontend (inline calc panel):
- Click any card body or rule pill → expand inline panel inside the
card (only one open at a time). Pill picker (radio chips) appears
when the card has 2+ rule pills; first preselected. Trigger date
defaults to today (m's Q3). Flag checkboxes auto-render from the
rule's condition_flag.
- Result row shows due date, "(N units from triggerDate)", and a
shift chip when wasAdjusted ("⚠ Verschoben vom … wegen UPC-
Sommerferien (27.7.–28.8.)").
- "Zu Akte hinzufügen" CTA → inline project picker → POST to existing
/api/projects/{id}/deadlines/bulk with a single-element array using
source='fristenrechner' (m's Q2: existing tag, no new audit category).
- Modifier-key clicks (Cmd/Ctrl/Shift/middle) preserve the legacy
drill-to-Pathway-A semantics via <a href> anchors. Trigger pills
(Wiedereinsetzung, etc.) keep the trigger-event drill — they don't
have a single rule to compute.
- Escape collapses the open card.
CSS: lime accent border on hover/expanded; dashed top border for the
calc panel; mobile-friendly grid for the pill picker.
UPC R.221 cost-appeal sequence (m's Q5) is wired in Phase C's seed
already; Phase B's pill picker renders both pills (leave-to-appeal +
notice-of-appeal) when the user hits one of those leaves.
Migration 052 fixes six concept↔leaf mismaps in the v3 seed and adds
three proactive entry leaves under spaetere-schriftsaetze.
1. cms-eingang.gericht.hinweisbeschluss — drop the response-to-
preliminary-opinion | DE_INF row. DE_INF (LG) has no
Hinweisbeschluss; the concept lives only in DE_NULL via PatG §83.
2. cms-eingang.gegenseite.upc-inf.klageschrift — drop the notice-of-
defence-intention | UPC_INF row. UPC has no such rule in the corpus;
R.23 reaction is captured by statement-of-defence directly.
3. UPC R.221 cost-appeal sequence (m's Q5): three leaves now surface
BOTH application-for-leave-to-appeal | UPC_COST_APPEAL (sort 100,
R.221.1, 15 days) AND notice-of-appeal | UPC_APP (sort 200,
conditional on leave granted, R.220.1). Replaces the wrong notice-of-
appeal | UPC_COST_APPEAL row that was silently dropping pills.
4. ich-moechte-einreichen.berufung.upc-coa-orders — replace the buggy
application-for-leave-to-appeal | UPC_APP_ORDERS (no rule for that
combo) with request-for-discretionary-review | UPC_APP_ORDERS
(R.220.3).
5. cms-eingang.gericht.anordnung — narrow request-for-discretionary-
review NULL → UPC_APP_ORDERS. R.220.3 review applies specifically
to the Anordnungen / 15-day track.
6a. reply-to-cross-appeal coverage: add UPC_APP rows under upc-{inf,
rev}.berufungsschrift so the reply leaf is reachable when the
opponent files an Anschlussberufung.
6b. New leaves under ich-moechte-einreichen.spaetere-schriftsaetze for
proactive entry: r116-eingaben (EPA R.116 final submissions),
anschlussberufung-upc (R.237), reply-to-cross-appeal-upc (R.238).
NO `RAISE EXCEPTION` coverage gate (m's Q7) — last night's outage was
caused by exactly that pattern in migration 049. Replaced with a Go-
side test in event_category_coverage_test.go that asserts every
category='submission' concept is reachable from at least one leaf
(except the prosecution-only exempt list: filing, request-for-
examination, approval-and-translation). Skipped without
TEST_DATABASE_URL; CI gates on it.
bescheid-mit-frist mapping deferred per m's Q4. Will land separately.
Migration verified via supabase MCP dry-run + ROLLBACK on the live
youpc DB; end-state matches design §3.2-§3.4.
The v3 B1 decision tree filter collapsed each leaf's
(concept_id, proceeding_type_code) tuple list down to a flat concept_id
slice in EventCategoryService.ConceptIDsForSlug, dropping the per-leaf
proceeding constraint. The search service then loaded pills by
concept_id only, so picking a UPC-specific leaf still surfaced DE/EPA/
DPMA pills for any shared concept (Klageerwiderung, Replik, Duplik,
Berufungsschrift). m's repro: choosing CMS-Eingang → Gegenseite →
UPC Verletzung leaked national submissions.
Confirmed via DB: at least 25 leaves were over-broad pre-fix.
Fix carries the tuple set end-to-end via a new subtreeFilter type with
parallel uuid[] / text[] arrays. The matview SQL now uses
unnest($cids, $procs) AS t(cid, pcode) to match each row against the
allowed tuples — a junction row with NULL proc encodes "any proc for
this concept" (used by cross-cutting concepts like Wiedereinsetzung).
EventCategoryService gains AllOutcomes() for browse-all so the root
view also respects junction tuples. allMappedConceptIDs is gone.
Tests: added 5 v4 subtests under TestDeadlineSearch covering m's
repro slug, multi-tuple narrowing, trigger-pill cross-cutting,
forum AND-narrowing, plus an invariant regression gate that walks
every leaf with non-NULL proc and asserts no pill leaks. Skipped
when TEST_DATABASE_URL is unset; existing v3 assertions unchanged.
No schema change. No migration. Ships independently of Phases B/C.
v4 addresses three concerns from m on 2026-05-05 in priority order:
1. Card-click → compute deadline → add-to-project (v3 cards were dead-ends).
2. Filter narrowing bug — slug → concept_id allow-list dropped per-leaf
proceeding_type_code, so picking "UPC infringement opposing party"
leaked DE/EPA/DPMA pills. Confirmed via DB query: 25+ leaves overbroad.
3. RoP-rigorous tree audit: 6 confirmed seed errors (Hinweisbeschluss
DE_INF mismap, notice-of-defence-intention UPC_INF mismap, three
cost-appeal notice-of-appeal mismaps, request-for-discretionary-review
needs UPC_APP_ORDERS narrowing), plus reply-to-cross-appeal coverage
gap and bescheid-mit-frist orphan.
Plan splits into three independent phases (A: filter fix, no schema; B:
card-click flow + new calculate-rule endpoint; C: taxonomy migration 052
without RAISE EXCEPTION coverage gates per last night's outage lesson).
Inventor → coder gate held: no production code in this commit.
The B1 decision tree exposed a "Skip this step" affordance on
intermediate non-leaf nodes that broke the narrowing model — clicking
it left the tree in a half-narrowed state with no clear UX intent.
Drop the button entirely; users who don't know an answer should pick
"Anderes / Sonstiges" or switch to B2 (filter mode).
The step-back button (and its sibling .fristen-b1-loosen-link in the
empty-result state) rendered with `color: var(--color-accent)` over a
transparent background — lime green text on cream is unreadable. Move
both to a secondary-button shape: hairline border, muted text, accent
on focus-visible. Both light and dark themes verified.
Touched:
- frontend/src/client/fristenrechner.ts: drop skip TSX + handler
- frontend/src/client/i18n.ts: drop "deadlines.pathway.b.tree.skip"
- frontend/src/i18n-keys.ts: drop the codegen key
- frontend/src/styles/global.css: split off .fristen-b1-skip selector
and replace the lime-text rule with a bordered secondary style
using --color-text-muted / --color-border (themed both ways)
When the user prints (browser dialog or any Drucken button) the page now
strips everything except the actual result content. Hidden: sidebar nav,
bottom-nav, top header, footer, breadcrumbs, all forms (.tool-input,
.filter-row, .entity-controls, search bars, gebühren-lookup, etc.), the
Fristenrechner pathway-fork buttons, B1 decision-tree cascade, B1/B2 mode
toggle, view toggle, result-action buttons, every <button>. Visible:
timeline / columns view / cost breakdown / gericht cards / entity tables
/ glossar entries / checklist items, plus the page heading + subtitle so
the printed page is identifiable.
Per-page print rules above (kostenrechner / gebühren / checklisten /
gerichte) keep their existing specifics; this block is the catch-all for
chrome those rules miss.
Verified via Playwright print emulation on /dashboard, /tools/kostenrechner,
/tools/fristenrechner (Verfahrensablauf list + Spalten view), /events.
Five m's-bookmark fixes on top of the B1 surface change:
1. Sort proceeding pills inside concept cards by real-world frequency.
New paliad.proceeding_types.display_order column (m's spec values:
UPC_INF=10, DE_INF=20, UPC_REV=30, ..., UPC_PI=920, ...). Default
999 for unmapped legacy codes. Search service surfaces it through
the deadline_search matview (rebuilt to add the column) and uses
it as primary key in pillSortKey, replacing the jurisdiction-rank.
2. Name standardisation: -klage → -verfahren on the proceeding-types
that describe a multi-step process. Specifically:
UPC_REV Nichtigkeitsklage → Nichtigkeitsverfahren
UPC_APP Berufung → Berufungsverfahren
DE_INF Verletzungsklage (LG) → Verletzungsverfahren (LG)
DE_INF_OLG, DE_NULL_BGH, DPMA_OPP, DPMA_BPATG_BESCHWERDE,
UPC_COST_APPEAL, UPC_APP_ORDERS, DPMA_BGH_RB, DE_INF_BGH —
same -verfahren standardisation.
3. legal_source for rev.defence × UPC_REV: was NULL, leaking the
internal local_code 'rev.defence' to the UI. Set to UPC.RoP.49.1
(Defence to Application for Revocation, R.49.1).
4. Frontend renderPill no longer falls back to rule_local_code when
legal_source is missing — the source span just collapses, so no
internal slug ever shows up as a "citation".
5. Quick-pick chips refactored to a slug-based array (QUICK_CHIPS) in
fristenrechner.tsx, single source of truth for both fork-shortcut
and B2-search-bar rows. Each chip carries data-chip-name-de /
data-chip-name-en; relabelChips() rewrites visible text per active
language. Dropped the duplicate "Statement of Defence" chip (same
concept as "Klageerwiderung"). Each chip now maps to one concept
slug — Klageerwiderung→statement-of-defence, Berufung→notice-of-
appeal, Einspruch→opposition, Replik→reply-to-defence,
Beschwerde→nichtzulassungsbeschwerde, Schadensbemessung→
application-for-determination-of-damages, Wiedereinsetzung→
wiedereinsetzung.
Migration 051 uses RAISE WARNING (not EXCEPTION) on coverage gates
per the 049 outage lesson — partial-migration recovery beats whole-
transaction failure. Matview rebuild stays inside the transaction;
RefreshSearchView() on next boot is a cheap no-op.
Pathway B B1 mode previously rendered an empty result area on every
state — the runB1Search() output target was #fristen-search-results,
which lives inside the B2 panel. When B2 is hidden (B1 active), the
results were written into a hidden subtree and never seen.
Changes:
- TSX: add #fristen-b1-results inside #fristen-b1-panel, below the
cascade button row.
- frontend/fristenrechner.ts: extract renderSearchResultsInto() and
wirePillClicks(); runB1Search now writes to fristen-b1-results,
fetches /api/.../search?browse=all when no slug is picked yet (full
landscape on entry), and applies CSS-driven loading dim with a seq
guard against out-of-order responses. Hoisted loadAndRenderB1() so
showBMode("tree") can trigger the tree load on Pathway B entry
(radio.checked = true does not fire change events).
- backend: SearchOptions.BrowseAll, allMappedConceptIDs() returning
the union of every concept reachable from any leaf via
paliad.event_category_concepts, lifted limit ceiling for browse
modes (default 200, max 500). Handler exposes ?browse=all.
- CSS: shared loading-state styling for fristen-b1-results.
Migration 049 went dirty in prod because the coverage gate at the end
(DO $coverage$) raised on 'reply-to-cross-appeal' — it's defined as a
submission concept but no leaf in the decision-tree seed maps to it.
reply-to-cross-appeal is a downstream-of-cross-appeal concept, only
reachable after the user has already entered the cross-appeal Pathway B
branch via 'response-to-appeal'. Adding a dedicated leaf would be
useful UX (file a follow-up), but for now exempting it from the
coverage gate matches the established 'pure-administrative' exemption
pattern used for filing / request-for-examination / approval-and-translation.
Manual recovery: set tracker version=48 dirty=false on prod (schema
from 048 was already applied via supabase MCP). Dokploy redeploy will
now run 049 + 050 cleanly and reach version=50.
Refs: t-paliad-133 prod outage 11:15-11:30 Tue 05.05.2026
m's spec lock §10 Q1 (2026-05-05): "Retire legacy tabs - we are only
resorting." This commit drops the .fristen-mode-tabs nav (Verfahrensablauf
+ Was kommt nach…) and the ?legacy=1 escape hatch. Pathway A becomes
Verfahrensablauf-only; the trigger-event panel (mode-event-panel) stays
in the DOM but is hidden by default and surfaces only via concept-card
pill drill-in (drillToTrigger flips the panels directly).
Frontend deltas:
- frontend/src/fristenrechner.tsx: drop .fristen-mode-tabs section;
rename mode-event-panel role/label to standalone tabpanel.
- frontend/src/client/fristenrechner.ts:
- drop isLegacyMode() + ?legacy=1 branch in showPathway().
- drillToTrigger() now flips procedure ↔ event panels directly
(no more #mode-event-tab click → handler chain).
- initModeTabs() bails on tabs.length===0 (already does); no
further changes needed.
- frontend/src/styles/global.css: drop .fristen-pathway-shell--legacy.
Backend untouched.
Build: clean. Frontend bundle 1473 keys unchanged. go build + vet +
tests pass.
The deadlines.mode.procedure / deadlines.mode.event i18n keys remain
in i18n.ts as orphans for now; cleaning them up is purely cosmetic
and lives outside the v3 scope.
Wires the v3 Gericht/System multi-select filter on the Pathway B/B2
panel. 10 forum-bucket chips per m's spec lock §10 Q8 (UPC CFI, UPC
CoA, DE LG/OLG/BGH/BPatG, EPA Erteilung/Einspruchsabt./Beschwerdek.,
DPMA).
UX:
- Chip click toggles its membership in activeForums Set.
- Multi-select; chips AND across the result set
(UNION within forum, AND with other filters — backend handles).
- ?forum=<comma-separated> URL state round-trips on every toggle.
- popstate restores active set; lang switch re-renders chip labels.
- Shared between B1 and B2: tree-mode reissues runB1Search;
filter-mode dispatches input event on the search box.
Frontend file deltas:
- frontend/src/client/fristenrechner.ts: FORUM_BUCKETS array,
activeForums Set, renderForumChips(), reissueSearchWithCurrentFilters()
(mode-aware), getActiveForumsParam() consumed at every search call.
- B2 search fetch + B1 cascade fetch both send ?forum= when active.
Frontend i18n keys for the 10 forum labels (DE+EN) shipped with
Phase B; this commit just renders them.
Backend was wired in Phase C; this commit completes the user-facing
path. Forum filter narrowing applies AND-wise with q / event_category_slug
/ proc / party / source — empty-result UX shows the existing "no hits"
status, m can drop a chip to widen.
Build: clean. Frontend bundle unchanged size delta (≈+50 lines, 1473 keys).
Phase D-2 (party-perspective selector + is_bilateral mirroring renderer)
ships next.
Reshapes /tools/fristenrechner into the v3 landing fork. Default
view: two big pathway cards (📖 Verfahrensablauf informieren
vs 📅 Frist eintragen aufgrund Ereignis) plus a quick-pick chip
shortcut row that jumps straight into Pathway B + filter mode +
prefilled query.
URL state machine:
- ?path=a → Pathway A (existing wizard, wrapped in fristen-pathway-a)
- ?path=b → Pathway B shell with mode toggle (B1 tree / B2 filter)
- ?mode=tree → B1 panel (stub for Phase B; Phase C wires the cascade)
- ?mode=filter → B2 panel (search bar + chips + concept-card results)
- ?path absent → landing fork
- ?legacy=1 → pre-v3 layout (legacy escape hatch; dropped in Phase E)
- localStorage remembers last-used pathway
Pathway B's B2 panel hosts the existing Phase D search bar (relocated
from page-top into the pathway shell). The forum-filter row + chips
container exist in the DOM hidden — Phase D wires them.
Pathway A wraps the existing Verfahrensablauf wizard (proceeding tile
grid + date input + timeline / columns view) plus the legacy "Was
kommt nach…" tab. Both keep working unchanged in this commit; tabs
retire entirely in Phase E.
Phase B B1 panel is a stub: "Der Entscheidungsbaum ist in Vorbereitung."
Phase C replaces it with the data-driven cascade.
Files:
- frontend/src/fristenrechner.tsx: landing fork + pathway shells
- frontend/src/client/fristenrechner.ts: pathway state machine,
URL parser, popstate restore, fork-chip → ?path=b shortcut
- frontend/src/client/i18n.ts: 30+ new keys (deadlines.pathway.*,
deadlines.filter.forum.*, deadlines.perspective.*) DE+EN
- frontend/src/styles/global.css: .fristen-pathway-fork,
.fristen-pathway-card, .fristen-pathway-shell, .fristen-mode-toggle,
.fristen-forum-filter, .fristen-forum-chip rules
Frontend build: clean (1472 i18n keys). go build + vet: clean.
The legacy tabs (Verfahrensablauf-Tab + Was kommt nach…) live inside
Pathway A and continue to work — m's spec lock §10 Q1 retires them
in Phase E, not now.
Backend layer for the v3 decision tree:
- internal/services/event_category_service.go (NEW)
- Tree(): nested tree of all active event_categories for the
Pathway B / B1 cascade UI. Uses single SELECT + in-memory
parent-child stitching; corpus is small (≤100 nodes).
- ConceptsForSlug(): recursive CTE walks descendants of a slug and
joins event_category_concepts to return the candidate concept
outcomes (with optional proceeding_type_code narrowing).
- ConceptIDsForSlug(): convenience reduction for
`WHERE concept_id = ANY(...)` queries against the existing
deadline_search matview.
- ProceedingCodesForSlug(): per-leaf proceeding-code narrowing for
Phase D's forum filter intersection.
- internal/handlers/fristenrechner_event_categories.go (NEW)
- GET /api/tools/fristenrechner/event-categories returning the
nested tree as JSON. Frontend will ETag-cache via localStorage.
- Wired EventCategory into handlers.Services + dbServices + main.go.
The existing /api/tools/fristenrechner/search handler stays
unchanged in this commit; Phase D will add ?event_category_slug=
and ?forum= query params on top.
Build + vet clean.
Three migrations land the data layer for the Fristenrechner v3 decision
tree (Pathway B / B1) plus the bilateral-rule flag for the new party-
perspective selector. All purely additive — no breaking changes to the
v2 (t-paliad-131) corpus.
Migration 048 — schema:
- paliad.event_categories: recursive taxonomy tree (parent_id self-FK,
unique slug as materialised dot-path, step_question_de/en on internal
nodes, is_leaf bool, optional emoji icon).
- paliad.event_category_concepts: many-to-many junction (leaf →
deadline_concepts) with optional proceeding_type_code narrowing.
UNIQUE NULLS NOT DISTINCT prevents duplicate (leaf, concept, NULL)
rows (PG 15+).
- paliad.deadline_rules.is_bilateral bool: when true AND
primary_party='both', the rule mirrors into both party columns of
the v3 columns view; otherwise 'both' resolves single-side via the
perspective selector.
Migration 049 — seed taxonomy:
6 root buckets (cms-eingang, muendl-verhandlung, beschluss-entscheidung,
frist-verpasst, ich-moechte-einreichen, sonstiges) with 70+ leaves and
115+ junction rows. Tree depth reaches 4 today (cms-eingang › gericht
› endentscheidung › <leaf>) but the schema supports unlimited depth
per design lock §10 Q2. Coverage gate at the end raises if any
category='submission' concept is unreachable from a leaf, except the
3 pure-administrative slugs (filing, request-for-examination,
approval-and-translation) that live on Pathway A only.
Migration 050 — bilateral backfill:
Tags exactly 4 genuinely-bilateral rules:
- de_null.stellungnahme (Stellungnahme zum Hinweisbeschluss, PatG §83.2)
- epa_opp.r79_further (Stellungnahme weiterer Beteiligter)
- epa_opp.r116, epa_app.r116 (Eingaben vor mündl. Verhandlung)
All other primary_party='both' rules (Berufungsfristen, Anschlussberufung,
…) are role-swap appeals that resolve via the perspective selector at
render time.
Schema dry-run validated end-to-end against Supabase PG 15.8.
Design ref: docs/plans/unified-fristenrechner-v3.md §4.1 + §10 Q12.
m approved all 12 open questions in one batch. Locked spec:
1. Legacy tabs RETIRED in Phase E.
2. Decision-tree depth UNLIMITED (was: 4 max). Property of
event_categories data, not hard-coded.
3. Clickable breadcrumb for navigation.
4. Partial-path bookmarks (?b1=...).
5. Multi-select forum filter, default 1 selected.
6. Path-matching cards at each step. Renamed "Pfad lockern" →
"Schritt zurück".
7. Emojis only, no separate colour treatment.
8. Forum buckets simplified to 10: UPC CFI + UPC CoA + DE LG/OLG/
BGH/BPatG + EPA Erteilung/Einspruchsabt./Beschwerdek. + DPMA.
m collapsed UPC LD/CD into UPC CFI (rules identical).
9. B1↔B2 share filter state.
10. Single branch / sequential commits / one final merge.
11. Party perspective default Claimant/Proactive; localStorage
remembers last-used. URL ?my_side= + ?appeal_filed_by=.
12. Bilateral rules tagged via new is_bilateral column on
deadline_rules; mirroring only when flagged.
Maria's two scope additions folded in:
- Court-system granularity for forum filter (clarification).
- Party-perspective selector absorbing t-paliad-132.
Implementation now starting on this branch.
m's 2026-05-05 brief restructures the page surface that v2 (t-paliad-131)
shipped. The current Fristenrechner stacks three blurred entrypoints —
Phase D search bar, Verfahrensablauf tile grid, "Was kommt nach…" tab.
v3 forks the page so each mental model has its own entry:
- Pathway A — Verfahrensablauf informieren (Browse): existing wizard.
- Pathway B — Frist eintragen aufgrund Ereignis (Event → Deadline),
subdivided into:
- B1 Entscheidungsbaum: data-driven button cascade (CMS-Eingang →
Vom Gericht → Hinweisbeschluss → cards), max 4 deep, back +
breadcrumb + bookmark URLs.
- B2 Filter / Suche: Phase D concept-card search PLUS new
Gericht/System multi-select chip filter (Q8 reversal). All filters
AND-narrow.
Adds two new tables (Phase A — purely additive):
- paliad.event_categories — recursive taxonomy tree, with step
questions on non-leaf nodes.
- paliad.event_category_concepts — leaf → concept junction with
optional proceeding_type_code narrowing.
Existing data layer (deadline_concepts, deadline_rules, trigger_events,
deadline_search matview) untouched. Phase D search handler gains
?event_category_slug= and ?forum= query params; forum-bucket map lives
in Go (UPC / DE LG / DE OLG / DE BGH / DE BPatG / EPA / DPMA).
Phasing: A (data) → B (landing fork) → C (B1 tree) → D (B2 forum
filter) → E (retire legacy tabs, gate-gated). Each phase independently
shippable.
Open questions for m at §10: retire legacy tabs, decision-tree depth,
back/breadcrumb, partial-path bookmarks, multi vs single-select forum,
all-vs-path-matching cards per step, austere icons, 7 forum buckets,
B1↔B2 state-sharing, PR phasing.
Inventor parked. Next: m's go/no-go before coder shift.
Cross-references docs/plans/unified-fristenrechner.md (v2, shipped) for
concept-layer / search-backend / coverage details v3 inherits unchanged.
Closes the user-facing half of the unified Fristenrechner. The proceeding
tile grid + the two existing modes (Verfahrensablauf / Was kommt nach…)
stay in place per m's "augment, not replace" — the search bar lives
above them and drills *into* either mode pre-selected.
frontend/src/fristenrechner.tsx:
- New search section above the mode tabs:
• search input with magnifier icon and clear (✕) button
• 8 quick-pick chips per design Q8 (Klageerwiderung · Berufung ·
Einspruch · Replik · Beschwerde · Statement of Defence ·
Schadensbemessung · Wiedereinsetzung)
• #fristen-search-results container the client renders cards into
- i18n keys live in deadlines.search.* with DE primary / EN mirror.
frontend/src/client/fristenrechner.ts:
- Search subsystem with the same debounce-and-sequence-counter pattern
the existing event-mode and procedure-mode calc paths use.
- GET /api/tools/fristenrechner/search?q=…&limit=12 with same-origin
credentials. Empty q clears results; failures fall back to the
"no hits" placeholder.
- Concept card layout: name + alt-language name, optional description,
"auch bekannt als" line for matched aliases, and one pill per
(proceeding × rule). Cross-cutting trigger pills (Wiedereinsetzung,
Versäumnisurteil, Schriftsatznachreichung, Weiterbehandlung) render
in a separate pills section labelled "Verfahrensübergreifend:".
- Pills are <a href="…drill_url"> elements so middle-click / cmd-click
opens in a new tab; the JS click handler intercepts plain clicks
and drills client-side:
• rule pill → activate procedure mode tab + selectProceeding(code)
+ pendingFocus(rule_local_code) so the next
renderProcedureResults scrolls to and pulses the
focused row (.fristen-focus-highlight, 2.4 s ease).
• trigger pill → activate event mode tab + selectTriggerEvent(id).
- URL state on ?q=… via history.replaceState; popstate restores.
Initial load reads ?q= from the URL so /tools/fristenrechner?q=foo
shareable links work.
- onLangChange re-fires the search so card / pill labels follow the
active locale (matches the existing onLangChange wiring for
procedure + event results).
frontend/src/styles/global.css:
- .fristen-search input + .fristen-search-chip + .fristen-search-icon
(magnifier inset 14px from the left, search-input padded 2.6rem
on the left to clear it).
- .fristen-card / .fristen-pill grid layout with party badges in the
project's existing accent palette (claimant blue, defendant red,
both grey, court amber). Mobile @media collapses the pill grid
to a 2-column shape so legal_source + duration stack cleanly.
- .fristen-focus-highlight keyframes for the post-drill pulse.
Out of scope for this shift (deferred):
- "Vollständige Instanzenkette" toggle (design Q5). The toggle is a
multi-stage timeline render that calls Calculate independently per
stage with one date input per stage anchor — a calculator-side
feature, not the search bar. Will land as a follow-up phase.
- Columns-view sequence preservation for undated court-set events
(design §7 "Out of scope — separate task" note). Already flagged
as a separate task to file.
Validation: `bun run build` clean (1443 i18n keys, no orphans);
`go build ./... && go vet ./... && go test ./internal/...` green
across all packages. The dist bundles confirm the new symbols
landed in fristenrechner.js (search wiring), global.css (48 hits on
new selectors), and fristenrechner.html (9 unique fristen-search-*
classes). Live browser verification with auth happens after merge —
the route is auth-gated and the playwright profile is held by
another process, so a static smoke test against the dist HTML
isn't representative of the rendered authenticated page.
Closes the search half of the unified Fristenrechner. Phase D (concept-card
UI on /tools/fristenrechner) follows in a subsequent shift.
Migration 047:
- Seed the missing `wiedereinsetzung` concept and re-point the four
Wiedereinsetzung trigger_events (200..203) at it. PR-7 referenced
the slug `re-establishment-of-rights` but never seeded the concept,
so the four cross-cutting triggers were dropping out of any concept-
JOINing query. Per m's slug rule (Q1: shared cross-cutting concepts
use DE slug because German term dominates HLC vocabulary).
- Create paliad.deadline_search materialised view: UNION ALL of
(deadline_rules joined to deadline_concepts) and (trigger_events
joined to deadline_concepts via slug). Trigram GIN indexes on
legal_source / concept_name_de / concept_name_en / rule_name_de /
rule_name_en / rule_code; gin (concept_aliases) for array
containment; UNIQUE INDEX on a synthetic row_key so refresh can
run CONCURRENTLY.
Refresh strategy: data only mutates via migration files at server
startup, so no AFTER triggers and no pg_cron — main.go calls
services.RefreshSearchView right after db.ApplyMigrations. CONCURRENTLY
keeps reads online and stays well under 100 ms at < 1k rows.
Service `internal/services/deadline_search_service.go`:
- Two-query pipeline per request: (1) rank concept_ids by
GREATEST(similarity()) across name / aliases / legal_source / rule_code
plus a 0.2 alias-hit boost; (2) load all matview rows for the top-N
concepts and assemble per-pill JSON.
- normalizeQuery strips legal-prefix noise (`§`, `Art.`, `Section`,
`Rule `) so users typing `§ 82` find DE.PatG.82.1 even though the
structured legal_source column doesn't carry the prefix.
- FormatLegalSourceDisplay renders structured codes back to the
pleading form HLC users expect:
UPC.RoP.23.1 → "UPC RoP R.23(1)"
DE.PatG.82.1 → "PatG §82(1)"
EU.EPÜ.108 → "EPÜ Art.108"
EU.EPC-R.79.1 → "EPC R.79(1)"
EU.RPBA.12.1.c → "RPBA Art.12(1)(c)"
- Drill URLs route per kind: rule pills → ?proc=…&focus=…, trigger
pills → ?mode=event&triggerId=…
Handler `GET /api/tools/fristenrechner/search?q=&party=&proc=&source=&limit=`:
- Returns the JSON shape from design §6.1 (cards-with-pills).
- 503 with friendly DE message when DATABASE_URL is unset, mirroring
the other Fristenrechner endpoints.
- Empty q returns an empty cards array (browse surface is Phase D).
Tests:
- Pure-Go: TestFormatLegalSourceDisplay (12 cases across all known
prefixes) + TestNormalizeQuery (8 cases).
- Integration (skipped without TEST_DATABASE_URL): golden table
pinning the design's binding queries — Klageerwiderung returns the
statement-of-defence card with UPC.RoP.23.1, DE.ZPO.276.1,
DE.PatG.82.1, EU.EPC-R.79.1, DE.PatG.59.3 pills; "RoP 23" returns
the same card; "§ 82" → normalized "82" → BPatG hit; Wiedereinsetzung
returns one card with exactly 4 trigger pills (ids 200..203);
party / source filters narrow as expected; limit cap honoured.
- SQL semantics validated against live data via supabase MCP using a
CTE-inlined matview definition with the slug fix simulated; results
match the golden table.
Per design doc `docs/plans/unified-fristenrechner.md` §4.6 (matview
shape) + §6 (search ranking + API).
PR-7 of the Unified Fristenrechner. Final Phase B migration. Closes
all named cross-procedural deadline gaps in the design.
These concepts fire across many proceedings (any patent application,
any civil case, any opposition, any appeal) and don't naturally belong
to one proceeding-tree timeline. Modelled per design §5.2.4 + §5.3 as
event-trigger-only entries: the user picks the trigger ("the moment
the obstacle was removed", "the date the Versäumnisurteil was served")
and the calculator returns the deadline.
Migration 046 adds 7 trigger_events (ids 200–206, paliad-native space
above the youpc-imported 1–114 range so future resync stays clean) and
7 corresponding event_deadlines + 3 new concepts.
WIEDEREINSETZUNG IN 4 LEGAL CONTEXTS (one shared concept slug
re-establishment-of-rights, seeded in PR-1):
- PatG §123(2): trigger 200, 2 months / max 1 year
- ZPO §234(1): trigger 201, **2 WEEKS** / max 1 year
← critical distinction; the 2-weeks-not-months ZPO
case is the most-confused detail of DE
Wiedereinsetzung. notes_de explicitly capitalises
"WOCHEN" so the user reads it before computing.
- EPC Art.122 + R.136(1): trigger 202, 2 months / max 12 months
- DPMA via PatG §123: trigger 203, 2 months / max 1 year
OTHER CROSS-CUTTING:
- Versäumnisurteil-Einspruch (ZPO §339): trigger 204, 2 weeks
Notfrist — keine Verlängerung möglich.
- Schriftsatznachreichung (ZPO §296a): trigger 205, 3 weeks
(court-set typical; placeholder the user can adjust via
click-to-edit if the court actually set a different period)
- Weiterbehandlung (Art.121 EPÜ + R.135): trigger 206, 2 months
Distinct from Wiedereinsetzung — niedrigere Gebühr, applies
BEFORE final loss of rights.
Three new concepts (slug naming per design §4.4):
- versaeumnisurteil-einspruch (DE-only procedure → DE slug)
- schriftsatznachreichung (DE-only → DE slug)
- weiterbehandlung (EPC-native + DE term dominates HLC vocab → DE slug)
Live-verified all 7 trigger_events on paliad.de (tester@hlc.de) via
the existing /tools/fristenrechner "Was kommt nach…" mode:
trigger 200 → 2026-07-06 (2mo PatG, weekend-shift)
trigger 201 → 2026-05-18 (2 WEEKS ZPO — the critical case)
trigger 204 → 2026-05-18 (2 weeks ZPO §339)
trigger 205 → 2026-05-26 (3 weeks ZPO §296a)
trigger 206 → 2026-07-06 (2mo EPC weiterbehandlung)
Out of scope (no calculator-relevant deadlines, would just be search
clutter): Mahnverfahren-Widerspruch (ZPO §345), Validierungsfristen
national (Art. 65 EPÜ → varies per state), Teilanmeldung (R.36 EPC →
"until end of pending parent" is anchor-on-revocation-of-grant).
Phase B is now complete. Phase C (search backend) + Phase D (concept-
card UI) follow per design.
PR-6 of the Unified Fristenrechner. Fills the EPA-side coverage gaps
named in the design + repairs three pre-existing EPA bugs surfaced
during this work.
Migration 045:
PRE-EXISTING BUG FIXES
1. EPA anchor convention bug. epa_opp.grant and epa_app.entsch were
seeded with party='court' + event_type='decision' → calculator's
isCourtDeterminedRule(r) returned true → those anchor rows
rendered as IsCourtSet (no date), propagating IsCourtSet to every
downstream rule that chained off them. Result on prod: EPA_OPP
showed "court-set" for Einspruchsfrist / Erwiderung / Entscheidung
instead of computed dates; ONLY the trailing beschwerde + begr
rendered dates (and only by accident, because they had parent_id=
NULL and computed off triggerDate directly).
Fix: changed both anchors to party='both' + event_type='filing' so
they render as IsRootEvent. Matches the convention I established
for DE_INF_OLG / DE_INF_BGH / DE_NULL_BGH / DPMA_BPATG_BESCHWERDE /
DPMA_BGH_RB anchors in PR-3/4/5.
2. EPA_OPP appeal-phase parent bug. epa_opp.beschwerde +
beschwerde_begr had parent_id=NULL → were computing 2mo and 4mo
from the GRANT date instead of from the OPPOSITION DECISION date.
Re-parented both on epa_opp.entsch. They now correctly render as
IsCourtSet placeholders (because entsch is court-set) until the
user enters the real decision date via the Phase A click-to-edit
affordance.
3. EPA_APP.erwidg modelling bug. Was parent_id=NULL + duration=0 +
party=both + event=filing → IsRootEvent → emitted the trigger date
as "Erwiderung". Now properly modelled per Art. 12(1)(c) RPBA 2020:
parent=epa_app.begr, duration=4 months, name="Beschwerdeerwiderung",
legal_source=EU.RPBA.12.1.c, response-to-appeal concept.
NEW COVERAGE (per design §5.3)
EPA_OPP gains 2 rules:
- epa_opp.r79_further: Stellungnahme weiterer Beteiligter
(R.79(2)/(3) EPC) — court-set, parent=erwidg
- epa_opp.r116: Eingaben vor mündl. Verhandlung
(R.116(1) EPC) — court-set, parent=entsch (so it surfaces in the
opposition phase but stays IsCourtSet until oral hearing date is
entered via override)
EPA_APP gains 2 rules:
- epa_app.r116: Eingaben vor mündl. Verhandlung
(R.116(1) EPC + Art. 13 RPBA) — court-set, parent=oral
- epa_app.r106: Antrag auf Überprüfung
(Art. 112a EPÜ) — 2 months from service of decision, parent=
entsch2 (the BoA decision)
Three new EN-slug concepts (UPC/EPC-native): r79-further-stellungnahme,
r116-final-submissions, petition-for-review.
Live-verified on paliad.de:
EPA_OPP trigger 2026-05-04 → grant IsRootEvent / Einspruchsfrist
2027-02-04 (9mo) / Erwiderung 2027-06-04 (4mo from frist) /
r79_further 2027-06-04 (filed-with-erwidg) / Entscheidung +
Beschwerde + Begründung + r116 IsCourtSet (waiting for entsch).
EPA_APP trigger 2026-05-04 → entsch IsRootEvent / Beschwerde
2026-07-06 (2mo, weekend-shift) / Begründung 2026-09-04 (4mo from
entsch) / Beschwerdeerwiderung 2027-01-04 (4mo from Begründung
per RPBA 12.1.c) / r116 IsCourtSet (parent=oral) / r106 IsCourtSet
(parent=entsch2, will compute 2mo from BoA decision once entered).
Out of scope (deferred to PR-7 cross-cutting): Wiedereinsetzung
(Art. 122 EPÜ + R.136 EPC), Weiterbehandlung (Art. 121 EPÜ + R.135 EPC),
Validierungsfrist national (Art. 65 EPÜ).
PR-5 of the Unified Fristenrechner. Three new proceeding types
covering the DPMA → BPatG → BGH opposition / appeal chain. Closes the
DPMA gap m named — paliad has had zero DPMA-specific timelines until
now (DPMA-granted patents in Nichtigkeit went to DE_NULL but the DPMA
opposition + Beschwerde + Rechtsbeschwerde chain had no home).
Migration 044 adds:
- DPMA_OPP (Einspruch DPMA, sort=310): 4 rules. Anchor "Veröffentlichung
der Erteilung" + Einspruchsfrist (PatG §59.1, 9mo) + Erwiderung
Patentinhaber (PatG §59.3, court-set ~4mo, party=defendant) +
DPMA-Entscheidung (court).
- DPMA_BPATG_BESCHWERDE (Beschwerde BPatG, sort=320): 5 rules. Anchor
"Zustellung DPMA-Entscheidung" + Beschwerde (PatG §73.2, 1mo) +
Beschwerdebegründung (PatG §75.1, 1mo from filing, extension on
request) + mündliche Verhandlung + BPatG-Entscheidung.
- DPMA_BGH_RB (Rechtsbeschwerde BGH, sort=330): 4 rules. Anchor
"Zustellung BPatG-Entscheidung" + Rechtsbeschwerde (PatG §100.1, 1mo)
+ Begründung (PatG §102 i.V.m. ZPO §551, 1mo from filing) +
BGH-Entscheidung.
Naming note: head's PR brief listed the third type as
"DPMA_BPATG_NICHTIGKEIT" but Nichtigkeitsklage is filed directly at
BPatG (already covered by DE_NULL — never chained off DPMA). The
natural BGH endpoint of the DPMA chain is the Rechtsbeschwerde per
§§ 100/102 PatG. Using DPMA_BGH_RB; trivially renamable if head
intended a different shape.
Two new DE-only concepts: rechtsbeschwerde (BGH legal appeal — DE-
specific procedure, no UPC/EPC equivalent), rechtsbeschwerde-
begruendung. Other rules reuse shared concepts (publication,
opposition, statement-of-defence, notice-of-appeal, statement-of-
grounds-of-appeal, oral-hearing, decision).
Frontend: new DPMA tile group in /tools/fristenrechner with 3 tiles,
positioned after the EPA group. 5 new i18n keys (DE+EN: deadlines.dpma
group label + 3 tile names + tile labels for 3 procs).
Live-verified all 3 trees on paliad.de (tester@hlc.de):
DPMA_OPP trigger 2026-05-04 → Einspruch 2027-02-04 (9mo) /
Erwiderung 2027-06-04 (4mo from Einspruch).
DPMA_BPATG_BESCHWERDE trigger 2026-05-04 → Beschwerde 2026-06-04
(1mo) / Begründung 2026-07-06 (1mo from Beschwerde, weekend-shift).
DPMA_BGH_RB trigger 2026-05-04 → Rechtsbeschwerde 2026-06-04 /
Begründung 2026-07-06.
PR-4 of the Unified Fristenrechner. Three new proceeding types so the
user can pick "I'm at OLG defending a Berufung" or "I'm at BGH on the
Nichtigkeitsberufung" and get the per-instance timeline directly,
rather than chaining off DE_INF / DE_NULL trailing rows.
Migration 043 adds:
- DE_INF_OLG (Berufung OLG, sort_order=210): 7 rules. Anchor
"Zustellung LG-Urteil" + Berufungsschrift (ZPO §517, 1mo) +
Berufungsbegründung (ZPO §520(2), 2mo, anchored on Urteil not on
notice) + Berufungserwiderung (ZPO §521(2), court-set 1mo typ.) +
Anschlussberufung (ZPO §524(2), filed-with-erwiderung) +
mündl. Verhandlung + OLG-Urteil.
- DE_INF_BGH (Revision/NZB BGH, sort_order=220): 8 rules. Anchor
"Zustellung OLG-Urteil" + parallel NZB (§544.1, 1mo) /
NZB-Begründung (§544.4, 2mo) / Revisionsfrist (§548, 1mo) /
Revisionsbegründung (§551.2, 2mo) — all four from the
OLG-Urteil-Datum since they're alternatives. Plus
Revisionserwiderung (§554, 1mo court-set) + mündl. + BGH-Urteil.
- DE_NULL_BGH (Berufung BGH gegen Nichtigkeit, sort_order=230): 6
rules. Anchor "Zustellung BPatG-Urteil" + Berufungsschrift
(PatG §110.1, 1mo) + Berufungsbegründung (PatG §111.1, 3mo) +
Berufungserwiderung (PatG §111.3 → ZPO §521.2, 2mo court-set typ.)
+ mündl. + BGH-Urteil.
Anchor convention: synthetic 0-duration root rule "Zustellung [prev-
instance] Urteil" with party='both' + event_type='filing' so it
renders as IsRootEvent (= the trigger date). Per design, this is the
honest model — the user enters the actual previous-instance Urteil
date, no fabricated inter-stage gap.
Four new DE-only concepts (per slug rule: DE for German-only
procedures): nichtzulassungsbeschwerde, nichtzulassungsbeschwerde-
begruendung, revisionsfrist, revisionsbegruendung. Other rules reuse
the existing shared concepts (notice-of-appeal, statement-of-grounds-
of-appeal, response-to-appeal, cross-appeal, oral-hearing, decision).
Frontend: 3 new tiles in DE_TYPES + 8 new i18n keys (DE+EN). Tiles
appear between DE_INF and DE_NULL per sort_order grouping.
Out of scope (kept in DE_INF / DE_NULL trees during transition until
Phase D Full Appeal Chain ships): the existing trailing rows
de_inf.berufung / de_inf.beruf_begr / de_null.berufung /
de_null.beruf_begr stay live so users picking those trees still see
the appeal-period entry. Phase D will gate the visibility.
Live-verified all 3 trees on paliad.de:
DE_INF_OLG trigger 2026-05-04 → Berufung 2026-06-04 (1mo) /
Begründung 2026-07-06 (2mo from Urteil, weekend-shift) /
Erwiderung 2026-08-06 (1mo from Begründung) / Anschluss
2026-08-06 (filed-with-erwiderung).
DE_INF_BGH trigger 2026-05-04 → NZB 2026-06-04 (1mo) /
NZB-Begr 2026-07-06 / Revision 2026-06-04 / RevBegr 2026-07-06
(parallel options) / RevErw 2026-08-06.
DE_NULL_BGH trigger 2026-05-04 → Berufung 2026-06-04 / Begr
2026-08-04 (3mo per PatG §111.1 = the now-fixed seed) / Erwidg
2026-10-05 (2mo from Begr, weekend-shift).
PR-3 of the Unified Fristenrechner. Three concerns bundled in migration
042 since they touch only DE_INF / DE_NULL trees and ship together
without coverage interactions:
1. PatG §111(1) bug fix. Current paliad seed had de_null.beruf_begr at
1 month. Current text of §111(1) BGBl. 2022: "Die Frist zur
Begründung der Berufung beträgt drei Monate. Sie beginnt mit der
Zustellung des in vollständiger Form abgefassten Urteils, spätestens
mit Ablauf von fünf Monaten nach der Verkündung." Bumped to 3 months
+ deadline_notes documenting the 5-month outer cap.
2. DE_NULL Hinweisbeschluss cycle (PatG §83). 4 new rules added between
Klageerwiderung and Mündliche Verhandlung:
- de_null.replik_klaeger (Replik, 2mo typical court-set, R.83.2)
- de_null.hinweisbeschluss (court order, R.83.1) — IsCourtSet
- de_null.stellungnahme (response, parent=hinweisbeschluss, R.83.2)
— IsCourtSet via parent propagation
- de_null.duplik (Rejoinder, 1mo typical court-set, R.83.2)
The court-set typical durations match the existing DE_INF replik/
duplik pattern — a placeholder date the user can override via the
Phase A click-to-edit affordance once the court actually sets it.
3. DE_INF Anzeige der Verteidigungsbereitschaft (ZPO §276(1) Satz 1).
New rule de_inf.anzeige, 2 weeks from Klage, defendant. Was the
biggest gap in the LG-civil cycle.
Three new concepts: preliminary-opinion (court order, sort 65),
response-to-preliminary-opinion (submission, sort 39),
notice-of-defence-intention (submission, sort 19). All seeded with
DE+EN aliases for search.
DE_INF + DE_NULL sequence_orders renumbered to leave gaps so future
inserts (B6 cross-cutting Wiedereinsetzung, B4-style instance-split)
can interleave without re-renumbering.
Live-verified on paliad.de (tester@hlc.de):
- DE_INF trigger 2026-05-04 → Anzeige 2026-05-18 (2w), Erwiderung
2026-06-15 (6w), backbone unchanged.
- DE_NULL trigger 2026-05-04 → Klageerwiderung 2026-07-06 (2mo),
Replik 2026-09-07 (2mo from Erwiderung, weekend-shift), Duplik
2026-10-07 (1mo from Replik), Hinweisbeschluss + Stellungnahme
IsCourtSet, Berufungsbegründung 2026-09-04 (3mo, was 1mo).
Out of scope (deferred to B6): cross-cutting Wiedereinsetzung,
Versäumnisurteil-Einspruch (only fires on default), Schriftsatz-
nachreichung. Out of scope (deferred to PR-4): new instance-split
proceeding types DE_INF_OLG / DE_INF_BGH / DE_NULL_BGH.
Closes m's primary complaint: today's `with_ccr` flag on UPC_INF only
swaps the Replik / Duplik durations. Per UPC RoP R.29 the with-CCR flow
ALSO adds 5–7 new submissions across the claimant / defendant exchange.
Same gap on UPC_REV: Application to amend (R.49.2.a → R.55 = R.32 m.m.)
and Counterclaim for infringement (R.49.2.b → R.50, R.56 cycle) were
entirely missing.
UPC_INF gets a nested `with_amend` flag under `with_ccr` (R.30 amend
is only available with a CCR). UPC_REV gets two parallel independent
flags `with_amend` + `with_cci`; both can be on. Citations verified
against data.laws_contents (youpcdb, UPCRoP).
Migration 041 (waved INSERTs because each subsequent rule references
the prior wave's parent_id):
- Wave 0: 11 new concept rows (counterclaim-for-revocation,
defence-to-counterclaim-for-revocation, defence-to-application-to-amend,
reply-to-defence-to-counterclaim-for-revocation,
reply-to-defence-to-application-to-amend,
rejoinder-on-reply-to-defence-to-ccr, rejoinder-on-reply-to-amend,
counterclaim-for-infringement, defence-to-counterclaim-for-infringement,
reply-to-defence-to-counterclaim-for-infringement,
rejoinder-on-counterclaim-for-infringement). counterclaim-for-revocation
also seeded for the search bar even though its rule lives implicitly
in inf.sod (the with_ccr flag captures it).
- UPC_INF + UPC_REV sequence_orders renumbered to leave gaps (10/20/30…)
so new cross-flow rows interleave chronologically with the backbone.
- 7 new UPC_INF rules: inf.def_to_ccr (R.29.a), inf.app_to_amend (R.30.1),
inf.def_to_amend (R.32.1), inf.reply_def_ccr (R.29.d),
inf.reply_def_amd (R.32.3), inf.rejoin_reply_ccr (R.29.e),
inf.rejoin_amd (R.32.3).
- 8 new UPC_REV rules: rev.app_to_amend (R.49.2.a), rev.def_to_amend
(R.43.3), rev.reply_def_amd (R.32.3 m.m.), rev.rejoin_amd (R.32.3 m.m.),
rev.cc_inf (R.49.2.b), rev.def_cci (R.56.1), rev.reply_def_cci (R.56.3),
rev.rejoin_cci (R.56.4).
Calculator (services/fristenrechner.go):
- Zero-duration rules now split into 4 buckets, not 2:
1. parent=nil + non-court → IsRootEvent (existing)
2. parent=nil + court → IsCourtSet (existing, e.g. inf.oral when stand-alone)
3. parent set + court → IsCourtSet (existing, waypoints)
4. parent set + non-court → "filed-with-parent" — inherit parent's
date. NEW. Used by rev.app_to_amend / rev.cc_inf which per
R.49(2) are filed AS PART OF the Defence to revocation.
- AnchorOverrides on a zero-duration rule short-circuits to the user's
date, propagating downstream as before.
Frontend:
- New checkboxes inf-amend-flag (UPC_INF, nested under ccr-flag),
rev-amend-flag, rev-cci-flag (UPC_REV). Visibility per proceeding
type; inf-amend disabled until ccr is on (R.30 dependency).
- Three new i18n keys (DE+EN). Small CSS for nested-checkbox indent
and disabled-state colour.
Live-verified via curl on paliad.de against tester@hlc.de:
UPC_INF + with_ccr+with_amend, trigger 2026-05-04 → all 7 new rules
render at correct dates (R.29.a 2mo, R.30.1 2mo, R.32.1 2mo from
app_to_amend, R.29.d 2mo from def_to_ccr, R.32.3 1mo, R.29.e 1mo,
R.32.3 1mo).
UPC_REV + with_amend+with_cci → rev.app_to_amend / rev.cc_inf show
rev.defence's date (filed-with-parent), R.43.3 2mo / R.56.1 2mo /
R.32.3 + R.56.3 1mo / R.32.3 + R.56.4 1mo all line up.
PR-1 of the Unified Fristenrechner. Purely additive: new search-grouping
layer + per-rule date override capability. No coverage changes yet
(those land in PR-2 = Phase B1 UPC counterclaim cross-flows).
Migrations:
- 037: paliad.deadline_concepts (id, slug, name_de/en, aliases text[],
party, category, sort_order). Trigram + GIN indexes for the search bar.
- 038: deadline_rules.concept_id (uuid FK), legal_source (text);
event_deadlines.legal_source; trigger_events.concept_id (text slug,
soft-link — youpc imports keep their bigint PK).
- 039: deadline_rules.condition_flag text → text[] (USING ARRAY[old]).
Semantic: rule renders iff every element is in CalcOptions.Flags.
Single-element arrays preserve the legacy with_ccr swap exactly.
- 040: seed 30 concept rows + backfill all 74 fristenrechner deadline_rules
with concept_id; backfill legal_source from existing rule_code
(e.g. 'RoP.023' → 'UPC.RoP.23.1', '§ 276 ZPO' → 'DE.ZPO.276.1',
'Art. 108 EPÜ' → 'EU.EPÜ.108', 'R. 79(1) EPÜ' → 'EU.EPC-R.79.1').
Calculator (services/fristenrechner.go):
- ConditionFlag is now pq.StringArray (matches text[] schema). New
allFlagsSet() helper gates rule rendering; rules with multi-element
flags require ALL of them set (prep for Phase B1 with_amend ∧ with_cci).
- CalcOptions.AnchorOverrides map[string]string (rule_code → YYYY-MM-DD).
The tree-walk consults overrideDates[parent.code] before reading the
computed-date map; lets a downstream rule re-anchor on a user-set date.
- IsCourtSet rows that get an override stop being placeholder and emit
the user's date as a real anchor (so downstream cost_app etc. compute
off it). New IsOverridden flag in UIDeadline so the UI can highlight
user-edited rows.
- LegalSource surfaced on UIDeadline for future search-card display.
UI (frontend/src/client/fristenrechner.ts + global.css + i18n):
- Each timeline / column rule date is click-to-edit. Click → inline
date input → blur or Enter → POST with anchorOverrides → re-render.
Empty value clears the override. Escape cancels. Root-event rows
(the trigger anchor) stay non-editable — that's the trigger-date input.
- Override map cleared on proceeding switch / reset; persists across
trigger-date / flag toggle changes within the same proceeding.
- New CSS: subtle hover underline on .frist-date-edit; lime border on
.timeline-date--overridden + .frist-date-edit-input.
- New i18n key deadlines.date.edit.hint (DE + EN).
Handler (handlers/fristenrechner.go):
- POST body gains optional anchorOverrides map<string,string>; passed
through to CalcOptions.
Tests:
- TestAllFlagsSet covers single-flag legacy semantic, two-flag AND
semantic, empty-required unconditional, extra-flags-no-effect.
- Existing TestIsCourtDeterminedRule unchanged.
Phase A ships standalone — Phase B1 (UPC counterclaim cross-flows) and
Phase C/D (search backend + concept-card UI) follow.
m's revisions (23:36):
- Q1 corrected: EN slug for shared concepts too (klageerwiderung →
statement-of-defence, replik → reply-to-defence, berufungsfrist →
notice-of-appeal, einspruchsfrist → opposition, wiedereinsetzung →
re-establishment-of-rights). DE slug only for German-law-only
concepts (nichtzulassungsbeschwerde, versaeumnisurteil-einspruch,
hinweisbeschluss-stellungnahme).
- Q4 simplified: drop the customizable-extension flag_param mechanism.
Replace with a generalised "user can override any computed date,
downstream re-anchors off it" capability. CalcOptions gains
AnchorOverrides map[string]string; tree-walk consults it before the
computed-date map. UI gives each row a click-to-edit date affordance
(also unlocks court-set decision dates being entered post-hoc, which
the existing IsCourtSet placeholder UX has been hinting at). PatG §82
seed stays at 2 months static; user-set extensions handled by inline
date override, not by a flag_param mechanism.
Cleaner. No new DB column. Generalises beyond extensions to any case
where the user knows the real date better than the calculator's
projection.
- Q1 concept slug naming: mixed convention. EN slug for UPC/EPC-native
concepts (application-to-amend, request-for-discretionary-review).
DE slug for German-only concepts (nichtzulassungsbeschwerde,
versaeumnisurteil-einspruch). DE slug for SHARED concepts that exist
in both DE and UPC/EPC (klageerwiderung, replik, berufungsfrist,
einspruchsfrist, wiedereinsetzung) because m works primarily in
German and the slug is internal/maintenance-facing only.
- Q2 EU.EPÜ confirmed for EPÜ namespace.
- Q3 PatG §111(1) 1mo→3mo confirmed for Phase B3.
- Q4 PatG §82(1): shape (b) — 1mo base + with_extension flag with
CUSTOMIZABLE extension duration (default 1mo). New flag_param
mechanism on flag-conditioned rules: CalcOptions.Flags becomes
map[string]int; rules with flag_param_code add caller's param to
duration. UI shows number input next to checkbox. Generalises to
PatG §75 etc. Phase A5 picks up the calculator extension; Phase B3
hooks PatG §82.
- Q5 Full Appeal Chain: multiple date inputs per stage, no inter-stage
gap guessing. Stage N's downstream deadlines render as IsCourtSet
placeholders until user enters Stage N-1's terminal decision date.
- Q6/Q7/Q8 confirmed as drafted.
§5.2.2 PatG §82 row updated to reflect flag-based shape. §4.4 concept
slug examples expanded with the mixed-convention rule rendered
explicitly. §7 Phase A5 added for the flag_param calculator change.
Significant restructure after m's 10 answers (relayed via head 23:10):
- Augment, not replace — search bar at top + existing tile grid stays as
browse fallback. Both existing tabs stay live. Phase E (subsumption)
dropped.
- Unifier shape: new paliad.deadline_concepts layer above existing
deadline_rules; deadline_rules gains concept_id FK + structured
legal_source. condition_flag scalar→array (Q3) for AND-of-flags
semantics on UPC_REV (with_amend ∥ with_cci).
- Search hits as ONE card per concept with proceeding pills inside (NOT
a flat list of one-per-proceeding hits). Card body: pills [UPC R.23.1
3mo] [LG §276.1 6w] [BPatG §82.1 1mo] [EPA R.79.1 4mo] etc.
- Structured legal_source codes: UPC.RoP.23.1, DE.ZPO.276.1,
EU.EPÜ.108, DE.PatG.111.1 — parseable, filterable, indexed.
- "Vollständige Instanzenkette" checkbox synthesises LG→OLG→BGH (or
BPatG→BGH) timeline as one tree at render-time; data stays per-
instance.
- Forum filter dropped (Q8). Filters now: Verfahrensart / Partei /
Rechtsquelle.
- Court-set placeholders ("Verhandlung", "Entscheidung",
"Zwischenverfügung") surface as searchable triggers (Q10).
- Columns-view sequence preservation (Q9) flagged but punted to a
separate follow-up task — t-paliad-129 column renderer must respect
sequence_order even on undated court-set events.
8 remaining open questions for m (concept slug convention, EPÜ
namespace, PatG §82(1) modeling, Full Appeal Chain anchor handoff,
quick-pick chip seed, etc.).