Commit Graph

456 Commits

Author SHA1 Message Date
m
b3401ec8ac feat(t-paliad-138): migration 054 — dual-control approvals schema
Schema-only commit (1 of 8) for the 4-Augen-Prüfung workflow per
docs/design-approvals-2026-05-06.md. No Go code reads these yet —
paliad behaves identically until commit 2 wires ApprovalService into
the mutation paths.

Migration 054 adds:

1. `senior_pa` to paliad.project_teams.role CHECK. Drops both the
   English `project_teams_role_check` and the German-legacy
   `projekt_teams_role_check` (live-DB constraint name carried over
   from migration 018's pre-rename era).

2. `paliad.approval_role_level(text) RETURNS int IMMUTABLE` — strict
   ladder helper: lead(5) > of_counsel(4) > associate(3) > senior_pa(2)
   > pa(1) > [local_counsel/expert/observer = 0 = ineligible]. Mirrors
   the upcoming Go `levelOf()`.

3. `paliad.approval_policies` (project_id, entity_type, lifecycle_event,
   required_role) — UNIQUE composite key gives at most 8 rows per
   project. RLS: SELECT via can_see_project; INSERT/UPDATE/DELETE only
   for global_admin (defence-in-depth; service-role pool bypasses RLS,
   so the actual gate is service-layer).

4. `paliad.approval_requests` — operational pending workflow.
   pre_image jsonb captures revert state; payload echoes the diff;
   required_role snapshots the policy at request time. CHECK
   `decided_by != requested_by` is the second layer of self-approval
   block. RLS = same can_see_project predicate as deadlines /
   appointments — anyone with project visibility sees pending requests.

5. `approval_status` (default 'approved'), `pending_request_id`,
   `approved_by`, `approved_at` columns on both deadlines and
   appointments. `appointments.completed_at` (new) lands the
   appointment:complete lifecycle event.

6. Backfill: every existing deadline + appointment row marked
   approval_status='legacy'. Per Q11, no retroactive approval; the
   next mutation on a legacy row that hits an active policy follows
   the normal flow.

Live-DB dry-run verified end-to-end: 20 deadlines + 5 appointments
backfill, both new tables instantiate cleanly, helper function returns
correct levels, self-approval CHECK fires on invalid INSERT, valid
pending insert succeeds.
2026-05-06 15:13:26 +02:00
m
7d1ddb9b84 docs(t-paliad-138): inventor design — dual-control approvals (4-eye)
Locked design for 4-Augen-Prüfung on Fristen + Termine. m-confirmed
decisions on all 11 open questions:

- Qualification gate reuses paliad.project_teams.role per-project
  (no new firm-wide axis). Adds new value `senior_pa` to the enum.
- Strict ladder: lead > of_counsel > associate > senior_pa > pa.
  Default required_role = associate. Per-project override allows pa-
  approves-pa or senior_pa-tier escalation.
- Per-(project, entity_type, lifecycle_event) policy grammar — up to
  8 settable rows per project in paliad.approval_policies.
- Edit-trigger allowlist = date-bearing fields only (Frist due_date /
  original_due_date / warning_date; Termin start_at / end_at).
- Write-then-approve: row mutates immediately, approval_status flips
  between approved/pending/legacy. Delete is the one stage-then-write
  exception (hard-delete on approve, restore on reject).
- Refuse + global_admin override on single-qualified-approver deadlock.
- Pending state visualised everywhere — list views, agenda, dashboard
  traffic-light, project detail, CalDAV-synced calendars (`[PENDING] `
  title prefix), email reminders.
- Bell + /inbox page with two tabs (zur Genehmigung / meine Anfragen).
- Operational paliad.approval_requests + audit lifecycle written to
  existing paliad.project_events (4 new event_types per entity).
- RLS = same can_see_project predicate; service layer enforces the
  approve/reject action gate. CHECK constraint blocks self-approval.
- Mark-legacy backfill: approval_status='legacy' on existing rows;
  next mutation flows through the gate.

Implementation phasing: single migration 054 + 8-commit PR plan
covering schema, service, wiring, policy authoring page, inbox,
pending pills, CalDAV/email integration, Verlauf rendering.

Inventor parked. Awaiting m go/no-go before any coder shift.
2026-05-06 14:58:01 +02:00
m
c1ceab7f4b Merge: t-paliad-122 — courts entity + per-country holidays (migration 053 + CourtService + HolidayService refactor) 2026-05-06 12:54:15 +02:00
m
733917aae2 feat(t-paliad-122): GET /api/tools/courts + Fristenrechner court picker
GET /api/tools/courts[?courtType=UPC-LD] returns the deadline-
computation slice of paliad.courts (id, code, names, country, regime,
court_type) — distinct from the rich Gerichtsverzeichnis at
/api/courts. Optional courtType filter narrows to a single tier.

POST /api/tools/fristenrechner and POST /api/tools/fristenrechner/
calculate-rule both accept an optional courtId field. When set, the
calculator resolves the court's (country, regime) and uses that
calendar; when omitted, the proceeding's existing jurisdiction column
seeds a sensible default — preserves today's behaviour for callers
that don't yet send a court.

Frontend: court-picker-row added to step 2 of the Fristenrechner
wizard. Visible only for proceeding types with multiple compatible
courts (today: every UPC-flavoured proceeding — UPC LDs span 12
countries, plus UPC CD seats and the CoA). DE-only proceedings (BPatG
nullity, BGH appeals, DPMA, EPA, EP grant) keep the form unchanged.
Picker re-runs the calc on selection so the user sees the same
deadlines shift to a different calendar without a manual click. i18n
key deadlines.court.label added for both DE and EN.

Default courts wired sensibly: UPC_INF / UPC_REV / UPC_PI etc. → UPC
LD München (HLC's home venue); UPC_APP / UPC_APP_ORDERS /
UPC_COST_APPEAL → UPC CoA Luxembourg; UPC_REV → UPC CD Paris.
2026-05-06 12:50:59 +02:00
m
d72990ad1b feat(t-paliad-122): country+regime aware HolidayService + CourtService
Holiday struct gains Country (ISO-3166) + Regime ('UPC' | 'EPO' | "")
fields. AppliesTo(country, regime) is the matching rule the new lookup
methods filter through: a row matches when its Country equals the
court's country OR its Regime equals the court's regime. UPC LD München
(DE+UPC) sees DE federal + UPC vacations; LG München (DE+"") sees only
DE federal; UPC LD Paris (FR+UPC) sees FR + UPC. germanFederalHolidays
fallback now country-tagged 'DE' so the per-country filter applies it
only to DE-jurisdictional callers.

Public service methods (IsHoliday, IsNonWorkingDay, AdjustForNonWorking
Days, AdjustForNonWorkingDaysWithReason, findVacationBlock) all take
(country, regime). Cache stays year-keyed — single DB hit per year, all
courts touching that year share it.

New CourtService loads paliad.courts once + answers Lookup(id),
CountryRegime(id, defaultCountry, defaultRegime), All(), ByCourtType(t).
FristenrechnerService.CalcOptions / CalcRuleParams gain CourtID;
EventDeadlineService.Calculate gains courtID. When courtID is empty,
DefaultsForJurisdiction maps the proceeding's existing jurisdiction
column to a sensible (country, regime) default — UPC proceedings get
(DE, UPC), everything else gets DE-only — preserving today's behaviour
for callers that don't yet send a court.

Tests: new TestAppliesTo_CountryRegimeFilter + TestAppliesTo_Rules
cover the cross-product of (DE court / UPC LD München / UPC LD Paris /
LG München) × (DE federal / UPC vacation / FR holiday). Existing tests
threaded through with ('DE', 'UPC') to preserve behaviour they were
written to lock.
2026-05-06 12:47:12 +02:00
m
a9d3695719 feat(t-paliad-122): migration 053 — courts entity + countries lookup + regime split
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.
2026-05-06 12:37:08 +02:00
m
bf06499d9c docs(t-paliad-122): inventor design — courts entity + per-country holidays
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.
2026-05-05 23:51:47 +02:00
m
98cb65f2cc Merge: t-paliad-136 Phase B — card-click → calc panel → add-to-project (v4 complete) 2026-05-05 14:09:02 +02:00
m
b54e938bdf feat(t-paliad-136): Phase B — card-click → calc panel → add to project
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.
2026-05-05 14:04:54 +02:00
m
6c3a6efc34 Merge: t-paliad-136 Phase C — RoP-rigorous tree taxonomy revision (migration 052 + Go coverage test) 2026-05-05 13:38:59 +02:00
m
d22ace1019 feat(t-paliad-136): Phase C — RoP-rigorous tree taxonomy revision
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.
2026-05-05 13:29:47 +02:00
m
7f292e5fa5 Merge: t-paliad-136 Phase A — filter narrowing carries (concept, proc) tuples end-to-end 2026-05-05 13:09:03 +02:00
m
b7470d7d77 fix(t-paliad-136): Phase A — filter narrowing carries (concept, proc) tuples
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.
2026-05-05 13:02:09 +02:00
m
30ac337a78 docs(t-paliad-136): Fristenrechner v4 inventor design
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.
2026-05-05 12:11:36 +02:00
m
25b4491681 Merge: t-paliad-135 — Print stylesheet (hide chrome/forms/buttons, show only result content) 2026-05-05 12:09:39 +02:00
m
3d905a0694 Merge: t-paliad-137 — Decision tree B1 remove Skip button + fix lime-on-light Step back contrast 2026-05-05 12:09:02 +02:00
m
19a1b8c942 fix(t-paliad-137): remove B1 "Skip step" + fix step-back contrast
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)
2026-05-05 12:07:06 +02:00
m
acaab22ad7 feat(t-paliad-135): print stylesheet — hide chrome, forms, buttons; show only result content
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.
2026-05-05 11:57:09 +02:00
m
931673337a Merge: t-paliad-134 v2 — pill ordering + name standardisation + chip dedup + legal_source fix 2026-05-05 11:54:13 +02:00
m
63eb5bde6f feat(t-paliad-134): pill ordering + name standardisation + chip dedup
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.
2026-05-05 11:53:13 +02:00
m
cc0059d050 Merge: t-paliad-134 — Fristenrechner v3 B1 result cards (browse-all + narrow-on-tree-click) 2026-05-05 11:42:54 +02:00
m
b32cfed37d feat(t-paliad-134): B1 surface — render concept cards beneath decision tree
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.
2026-05-05 11:39:30 +02:00
m
ff36528148 fix(t-paliad-133): add reply-to-cross-appeal to coverage exempt list
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
2026-05-05 11:22:14 +02:00
m
f40b652d01 Reapply "Merge: t-paliad-133 — Fristenrechner v3 (Pathway A/B fork + B1 decision tree + B2 forum filter + retire legacy tabs)"
This reverts commit 5bd17de732.
2026-05-05 11:18:38 +02:00
m
5bd17de732 Revert "Merge: t-paliad-133 — Fristenrechner v3 (Pathway A/B fork + B1 decision tree + B2 forum filter + retire legacy tabs)"
This reverts commit f7d72ff1d3, reversing
changes made to 1ea983f0c7.
2026-05-05 11:17:58 +02:00
m
f7d72ff1d3 Merge: t-paliad-133 — Fristenrechner v3 (Pathway A/B fork + B1 decision tree + B2 forum filter + retire legacy tabs) 2026-05-05 11:15:46 +02:00
m
568bc99a36 feat(t-paliad-133): Phase E — retire legacy mode tabs
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.
2026-05-05 11:07:41 +02:00
m
c399caff75 feat(t-paliad-133): Phase D-1 — B2 forum filter chip UI + URL state
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.
2026-05-05 11:05:37 +02:00
m
7141f4a954 feat(t-paliad-133): Phase C — B1 decision tree cascade + search extension
Wires the v3 Pathway B / B1 decision-tree cascade end-to-end. The
existing Phase D search backend gains two new query params, and the
frontend gets a data-driven button-cascade UI that walks
paliad.event_categories step-by-step.

Backend extension:
- internal/services/deadline_search_service.go
  - SearchOptions gains EventCategorySlug + Forums fields.
  - DeadlineSearchService gains an EventCategoryService dependency
    via SetEventCategoryService(); wired in main.go after both
    services exist (cross-link order).
  - ForumToProceedingCodes map (10 buckets per m's spec lock §10 Q8)
    translates v3 forum slugs to proceeding_type codes. Lives in Go
    so rebucketing = code change, not migration.
  - browseRanks() new query path: when q is empty AND
    EventCategorySlug is set, synthesise rank rows from the slug's
    reachable concept_ids — no trigram, just sort by
    concept_sort_order. Drives B1 narrowing.
  - rankConcepts() + loadPills() gain optional concept_id allow-list
    + forum_codes filters via UNIQUE NULLS NOT DISTINCT-shaped IS-NULL-OR
    PARAM clauses. Trigger pills (kind='trigger') always pass forum
    filter — they're cross-cutting by design.

- internal/handlers/fristenrechner_search.go
  - Reads new ?event_category_slug= and ?forum= (comma-separated)
    query params. Forwards to SearchOptions.
  - parseCSV() helper.

Frontend B1 cascade:
- frontend/src/client/fristenrechner.ts
  - loadEventCategoryTree(): one-shot fetch + in-memory cache of
    /api/tools/fristenrechner/event-categories.
  - renderB1Cascade(slug): renders breadcrumb + step question +
    button row + skip-step + step-back. Buttons walk down, breadcrumb
    walks back. Empty path = root question + 6 root buttons.
  - runB1Search(slug): hits /api/tools/fristenrechner/search?event_category_slug=
    and reuses Phase D's renderSearchResults() for the card list.
    Empty-result path shows "Schritt zurück" link (m's spec lock §10 Q6
    rephrase from "Pfad lockern").
  - URL state ?b1=<dot-path> round-trips. popstate restores cascade.
  - Pathway B default mode flips from filter → tree (B1 is now the
    discovery surface; B2 is for power users).

Frontend i18n: +1 key (deadlines.pathway.b.tree.start_question).

Frontend CSS: .fristen-b1-breadcrumb, .fristen-b1-crumb,
.fristen-b1-question, .fristen-b1-buttons, .fristen-b1-button (with
--leaf modifier border-left accent), .fristen-b1-skip,
.fristen-b1-step-back rules.

Frontend build clean (1473 keys). go build + vet + tests clean.
2026-05-05 11:03:34 +02:00
m
1182771fed feat(t-paliad-133): Phase B — landing fork UI + URL state
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.
2026-05-05 10:56:58 +02:00
m
2c770ef02f feat(t-paliad-133): Phase A — EventCategoryService + handler + route
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.
2026-05-05 10:51:58 +02:00
m
4d820892e8 feat(t-paliad-133): Phase A — event taxonomy schema + seed + bilateral flag
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.
2026-05-05 10:49:18 +02:00
m
7e363ac01d design(t-paliad-133): lock v3 design with m's answers (10:33)
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.
2026-05-05 10:37:44 +02:00
m
2ed476dc64 design(t-paliad-133): Fristenrechner v3 — Pathway A vs Pathway B fork
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.
2026-05-05 10:21:20 +02:00
m
1ea983f0c7 Merge: t-paliad-131 Phase D — concept-card UI on /tools/fristenrechner (FINAL — full unified Fristenrechner shipped) 2026-05-05 05:13:10 +02:00
m
1e5df8201b feat(t-paliad-131): Phase D — concept-card UI on /tools/fristenrechner
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.
2026-05-05 05:04:53 +02:00
m
7bd223ecd9 Merge: t-paliad-131 Phase C — search backend (matview + service + handler) 2026-05-05 04:42:40 +02:00
m
b45278b060 feat(t-paliad-131): Phase C — search backend (matview + service + handler)
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).
2026-05-05 04:32:50 +02:00
m
16c991288f Merge: t-paliad-131 Phase B6 — cross-cutting concepts (Wiedereinsetzung × 4 + Versäumnis + Schriftsatznachreichung + Weiterbehandlung) 2026-05-05 03:57:41 +02:00
m
53d5e5306c feat(t-paliad-131): Phase B6 — cross-cutting concepts (Wiedereinsetzung × 4 + Versäumnis + Schriftsatznachreichung + Weiterbehandlung)
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.
2026-05-05 03:46:45 +02:00
m
8c64344126 Merge: t-paliad-131 Phase B5 — EPA gap-fill (R.79.2/3, R.116, R.106) + EPA_OPP/APP anchor fix 2026-05-05 03:27:51 +02:00
m
706afb617f feat(t-paliad-131): Phase B5 — EPA gap-fill (R.79.2/3, R.116, R.106) + EPA_OPP/APP anchor fix
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Ü).
2026-05-05 03:17:46 +02:00
m
a9531afbf4 Merge: t-paliad-131 Phase B4 — DPMA proceeding chain (DPMA_OPP + DPMA_BPATG_BESCHWERDE + DPMA_BPATG_RECHTSBESCHWERDE) 2026-05-05 02:57:42 +02:00
m
25076142f4 feat(t-paliad-131): Phase B4 — DPMA proceeding chain
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.
2026-05-05 02:48:31 +02:00
m
d747046bf0 Merge: t-paliad-131 Phase B3 cont — DE instance-split proceeding types (OLG/BGH branches) 2026-05-05 02:27:38 +02:00
m
e3b093d9a2 feat(t-paliad-131): Phase B3 cont — DE instance-split proceeding types
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).
2026-05-05 02:19:37 +02:00
m
9a4f45fe48 Merge: t-paliad-131 Phase B3 — DE expansion (PatG §111 fix + BPatG Hinweisbeschluss + ZPO Anzeige) 2026-05-05 01:57:43 +02:00
m
24e22511ec feat(t-paliad-131): Phase B3 — DE expansion (PatG §111 fix + BPatG Hinweisbeschluss + ZPO Anzeige)
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.
2026-05-05 01:49:01 +02:00
m
3b595390c7 Merge: t-paliad-131 Phase B1 — UPC counterclaim cross-flows (R.25/R.30/R.49(2)/R.50) 2026-05-05 01:27:44 +02:00
m
cc68ab2873 feat(t-paliad-131): Phase B1 — UPC counterclaim cross-flows
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.
2026-05-05 01:25:03 +02:00