ba3e0795f88f8dcdd9dd32095d87b64c882242e9
1232 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| ba3e0795f8 |
feat(fristenrechner): Slice S6 — drop cascade endpoint, neutralize legacy Pathway B (m/paliad#146)
Cleanup pass per design §7 / S6, executed as a measured first cut
that drops the cascade endpoint + neutralizes the legacy Pathway B
row-stack / cascade init without lifting the entire ~1500 LoC
subtree out of `fristenrechner.ts`. The dead helpers stay for one
follow-up that can lift them safely.
Backend:
* Deleted `internal/handlers/fristenrechner_event_categories.go`.
* Dropped the `GET /api/tools/fristenrechner/event-categories`
route from `handlers.go`. The `EventCategoryService` itself
stays — it still backs the legacy concept-card search's
`?event_category_slug=` filter, which dies in the same
follow-up that removes the concept-card response shape.
* `paliad.event_categories` TABLE is untouched per design §7
(kept for future tools).
Frontend:
* `loadEventCategoryTree()` reduced to a stub returning `[]` — the
endpoint it fetched no longer exists, and no overhaul surface
calls it.
* `initB1Cascade()`, `initForumFilter()`, `initInboxFilter()`
early-return. Their `DOMContentLoaded` registrations stay so
the bundle exports are stable, but no Pathway B cascade /
chip-strip / inbox-channel wiring fires in `?legacy=1` mode.
* The Pathway B markup in `fristenrechner.tsx` stays in place; it
renders inert when a user hits `?legacy=1&path=b`.
* `buildRowStack`, `renderRowStack`, `runB1Search`, and the row-
stack helper functions remain as unreachable code. Removing
them mechanically requires retiring the entire upper-half
Pathway B B2 search wiring (`runSearch` + `renderConceptCard`
+ `renderSearchResults` + `SearchResponse` types) which is
tangled with the legacy concept-card response shape — deferred
to a follow-up that lands together with the backend
concept-card removal.
Verified — bun build clean (2971 i18n keys unchanged), 256
frontend tests pass, go build + vet clean, live-DB tests
(TestListProceedings, TestSearchEvents, TestLookupFollowUps)
still green.
Follow-up scope tracked in design §7 S6 — pending the helper-tree
lift and the legacy concept-card response-shape removal from
/search.
|
|||
| 4571bd4980 |
feat(fristenrechner): Slice S5 — flip overhaul default; legacy under ?legacy=1 (m/paliad#146)
`isOverhaulMode()` now returns true unless the URL carries `?legacy=1`. The overhaul UI from S2-S4 (mode tabs + Mode A search + Mode B wizard + shared result view) becomes the default landing for /tools/fristenrechner; the legacy three-step wizard + Pathway A/B + cascade is reachable only via the explicit `?legacy=1` opt-out for the two-week deprecation window before S6 drops the legacy code paths entirely. The pre-existing `?overhaul=1` deep links from S2-S4 still resolve — the detector treats *absence* of `?legacy=1` as overhaul, so bookmarks stay valid. No sidebar / header / outbound link change needed: those all point at the bare `/tools/fristenrechner` URL, which now boots overhaul. Verified — bun build clean (2971 i18n keys unchanged), 256 frontend tests pass, go build + vet clean. |
|||
| 70985d88b0 |
feat(fristenrechner): Slice S4 — Mode B wizard (m/paliad#146)
Mode B "🧭 Geführt" — the guided 3-5 row wizard defined in
docs/design-fristenrechner-overhaul-2026-05-26.md §3.2. Lands the
user on a single procedural_event (the trigger), then transitions
to the shared §4 result view.
Frontend:
* `fristenrechner-wizard.ts` — row stack with R1..R5:
R1 Was ist passiert? (event_kind, always asked)
R2 Vor welchem Gericht? (jurisdiction, skip if R1 narrows)
R3 In welchem Verfahren? (proceeding_type, auto-skip when
narrowed pool has 1 option)
R4 Welches Schriftstück? (procedural_event, landing)
R5 Welche Seite vertreten Sie? (party, only when follow-ups
differ by primary_party)
Row badges per §11.Q3: R1+R2 = Filter, R3+R4+R5 = Qualifier.
R5 has NO "Beide" option per §11.Q8 — Mode B is the file-mode
where perspective is a qualifier.
* Project prefill — derives R3 + R2 jurisdiction from
project.proceeding_type, R5 from project.our_side. Annotates
pre-filled rows with "aus Akte" tag and implicit rows with
"implizit" tag per §11.Q10 ("erhalten" annotation when a pick is
carried across an upstream change).
* R4-to-result transition — after R4 the wizard fetches /follow-
ups (no dates) to inspect primary_party variance. If both
claimant and defendant rules exist AND R5 isn't already set,
swaps the loading row for the R5 chip picker. Otherwise jumps
straight to mountResultView.
* URL state — `?mode=wizard&kind=…&forum=…&pt=…&r4=…&party=…`
keeps deep-link / back-nav consistent (the launchResult step
sets `event=` so the result view picks up).
* `fristenrechner-result.ts` mountModeShell now dispatches the
"wizard" tab to the wizard module (was a coming-soon
placeholder).
* 18 i18n keys added (DE + EN parity), 145-line CSS block for the
wizard row stack with Filter / Qualifier badge styling and
"aus Akte" annotation chip.
Backend:
* `ProceedingListOptions.EventKind` adds an EXISTS subquery
filter on `paliad.sequencing_rules` ⨯ `paliad.procedural_events`
so Mode B R3 chips only show proceedings whose event roster
contains at least one event of the requested kind (design
§6.3). Endpoint param: `event_kind=` on
/api/tools/proceeding-types.
Test updates:
* `TestListProceedings` switched from SKIP-when-column-missing to
asserting the live filter — mig 153 has landed, `kind` column
is in place. New subtests: kind=proceeding includes
upc.inf.cfi and excludes the phase row upc.cfi.interim;
event_kind=filing narrows to proceedings with filing events.
* `fristenrechner-wizard.test.ts` covers
`followUpsDifferByParty` — the R5 trigger predicate. 7 cases:
asymmetric → true; uniform / both / court / empty → false.
Verified — bun build clean (2971 i18n keys), 256 frontend tests
pass (incl. 7 new), go build + vet clean, live-DB
TestListProceedings passes all 6 subtests against mig 153 data.
|
|||
| 06d6c7540e |
Merge: t-paliad-323 Slice S3 — Fristenrechner Mode A direct search (m/paliad#146)
knuth shipped S3 of the Fristenrechner overhaul (design §3.1, §7-S3):
- New frontend/src/client/fristenrechner-mode-a.ts (507 LoC) — 'Direkt suchen' UI per design §3.1: Filter strip (Forum · Verfahren · Was passierte · Partei) with section-split visual hierarchy per m §11.Q3, free-text search box, ranked result list of procedural_events with click-to-lock-as-trigger.
- Inbox channel as secondary 'Erweitert' chip per §3.3 with CMS→UPC / beA→DE forum nudge.
- Mode tabs pair (Direkt suchen / Geführt) under Step-0 per §11.Q2; wizard tab placeholder until S4.
- Backend ListProceedings(jurisdiction, kind) — kind='proceeding' filter targets mig 153's column (just merged in
|
|||
| 3e55ff8294 |
Merge: t-paliad-325 — mig 153 proceeding_types kind discriminator + ProjectService hardening (m/paliad#147)
ritchie shipped atlas's design (docs/design-proceeding-types-taxonomy-2026-05-26.md):
- mig 153 additive: ADD COLUMN kind text NOT NULL DEFAULT 'proceeding' CHECK in {proceeding,phase,side_action,meta}; UPDATE 4 phase + 10 side_action + 9 meta; per m's Q9 flips is_active=false on the same 23 rows in the same TX. CHECK trigger projects_proceeding_type_kind_check blocks projects.proceeding_type_id from pointing at non-proceeding kinds. Snapshot to paliad.proceeding_types_pre_153 in the same TX. set_config('paliad.audit_reason', ...) defensively.
- ProjectService.SetProceedingType hardened: new ErrInvalidProceedingTypeKind, single-SELECT validator checks category + kind + is_active before assigning.
- 4-angle test (TestProjectService_ProceedingTypeKindGuard) covers happy-path proceeding, rejected phase, rejected inactive, rejected wrong category.
- cmd/gen-upc-snapshot/main.go gains the AND kind='proceeding' filter; embedded snapshot JSON regen flagged as follow-up (needs DATABASE_URL at runtime).
Mode B R3 query now becomes WHERE is_active=true AND kind='proceeding' for a 23-row clean primary list. Phase/side_action/meta rows survive in the table for taxonomic reference but never surface in pickers.
|
|||
| 9d688459e3 |
feat(db): mig 153 — proceeding_types kind discriminator + ProjectService hardening
Adds a `kind` column to paliad.proceeding_types (proceeding / phase /
side_action / meta) so the Mode B R3 Fristenrechner wizard, the
projects.proceeding_type_id binding, and the pkg/litigationplanner
snapshot can filter to primary proceedings only.
Implements the ratified design from docs/design-proceeding-types-
taxonomy-2026-05-26.md (m greenlit 2026-05-27 09:57 after the 11-question
AskUserQuestion round-trip).
Mig 153 is purely additive — ADD COLUMN with a safe DEFAULT, UPDATEs
reclassify 23 non-primary rows (4 phase + 10 side_action + 9 meta), and
a BEFORE INSERT/UPDATE trigger on paliad.projects backstops the new
invariant. Pre-mig audit (Supabase MCP, 2026-05-27) confirmed zero
downstream pressure on the 23 reclassified rows.
- internal/db/migrations/153_proceeding_types_kind.up.sql + .down.sql
- snapshot to paliad.proceeding_types_pre_153 in the same TX
- set_config('paliad.audit_reason', …) defensively
- DO-block asserts 23 reclassified rows before the trigger ships
- Q9 carve-out: is_active=false on every phase/side_action/meta row
- new trigger paliad.projects_proceeding_type_kind_check on
paliad.projects.proceeding_type_id
- internal/services/project_service.go
- extend validateProceedingTypeCategory to also enforce
kind='proceeding' AND is_active=true; new typed error
ErrInvalidProceedingTypeKind
- single SELECT picks up category + kind + is_active
- internal/services/project_service_test.go
- TestProjectService_ProceedingTypeKindGuard covers service-layer
rejection, the active-but-non-proceeding edge, mig 153 trigger
backstop, and the kind='proceeding' happy path
- cmd/gen-upc-snapshot/main.go
- filter proceeding_types query to kind='proceeding' for forward-
compat (the embedded UPC snapshot JSON regen requires DATABASE_URL
access and will land in a follow-up; the current placeholder is
already empty of non-primary rows)
t-paliad-325 / m/paliad#147
|
|||
| 2a2c5b8033 |
feat(fristenrechner): Slice S3 — Mode A direct search (m/paliad#146)
Mode A "⚡ Direkt suchen" — the power-user entry path defined in docs/design-fristenrechner-overhaul-2026-05-26.md §3.1. Renders above the §4 result view; clicking a result row locks the trigger event and transitions to the shared result surface from S2. Frontend: * `fristenrechner-mode-a.ts` — filter strip (Forum / Verfahren / Was passierte / Partei) + free-text search input + result list. Section-split visual hierarchy per m §11.Q3: filter chips in a bordered "Filter (eingrenzen)" strip on top, result list below. Inbox channel chip lives behind an "Erweitert" details summary per §3.3; picking CMS / beA auto-nudges the Forum chip. Party chip retains a "Beide" option (Mode A is filter mode per §11.Q8; Mode B drops it in S4). * `fristenrechner-result.ts` — new `mountModeShell(activeTab)` renders the two mode tabs per §11.Q2 and lazy-imports Mode A. Mode B tab is a placeholder until S4 lands. * `fristenrechner.ts` boot — when `?overhaul=1` is set and `?event` is empty, mountModeShell takes over (default tab = search; `?mode= wizard` opens the wizard tab when S4 ships). With `?event=` the flow still jumps straight to the result view. URL state syncs forum / pt / kind / party / q on every chip click. * 28 i18n keys added (DE + EN parity), 310-line CSS block for the mode tabs + Mode A surface. Backend: * New `ProceedingListOptions { Jurisdiction, Kind }` + service method `ListProceedings(ctx, opts)`. Legacy `ListFristenrechnerTypes` keeps the no-filter signature for existing callers. Handler `/api/tools/proceeding-types` accepts `?jurisdiction=` and `?kind=` query params. * `kind=proceeding` filter targets the taxonomy column landed in mig 153 (parallel branch t-paliad-325, m/paliad#147). Sequenced per the taxonomy doc §7 option (c): mig 153 merges before S3 ships to main, so the filter is never false-positive (no phase / side_action / meta rows leak into the chip strip). Verified — bun build clean (2955 i18n keys, data-i18n attributes clean), 249 frontend tests pass, go build + vet clean. New TestListProceedings — 4 PASS (no-filter, jurisdiction=UPC, jurisdiction=DE, ListFristenrechnerTypes alias) + 1 SKIP for the kind=proceeding case that probes the column and skips when mig 153 hasn't landed yet. S1 + S2 live tests still green. |
|||
| 058a36976b |
Merge: t-paliad-324 — proceeding_types taxonomy design doc (docs only) (m/paliad#147)
atlas shipped the 580-line design ratifying Model 1 (kind discriminator) for the proceeding_types cleanup. All 11 PRDs answered by m in §10.
Final categorisation (46 active rows):
- 23 kind='proceeding' (18 with corpus + 5 unloaded primaries incl. upc.costs.cfi per m's Q2 carve-out)
- 4 phase (upc.cfi.interim/oral/decision + upc.default.cfi)
- 10 side_action (evidence/experiments/security/intervention/parties/optout/inspection/freezing/withdrawal/rehearing)
- 9 meta (case.mgmt, general.rop, service, language, representation, fees, legalaid, special, reestablishment)
Mig 153 sketch (per §3): ADD COLUMN kind text NOT NULL DEFAULT 'proceeding' CHECK in {proceeding,phase,side_action,meta}; 4 UPDATEs setting kind for the non-primary IDs; optional CHECK trigger blocking projects.proceeding_type_id from referencing non-proceeding kinds. No row moves, no FK churn — 0 downstream rules / projects / spawn FKs / concepts point at non-primary rows today (verified live, §0.1).
Sequencing (m's Q10): parallel-land with knuth's S3 of the Fristenrechner overhaul. The kind column makes Mode B R3's WHERE filter trivial; no need to serialize.
Coder gate held — atlas parks; head dispatches a fresh Sonnet coder for mig 153 + ProjectService.SetProceedingType hardening + youpc-go snapshot regen.
|
|||
| 3219bff4d4 |
design(taxonomy): proceeding_types kind discriminator + 11 m's decisions (t-paliad-324)
Live audit established that 28 of 46 active proceeding_types have zero downstream pressure (0 rules, 0 projects, 0 spawn FKs, 0 concepts). Mig plan is purely additive: ADD COLUMN kind text CHECK (...), four UPDATE statements to tag phase/side_action/meta rows, deactivate them, and add a BEFORE INSERT/UPDATE trigger on projects.proceeding_type_id to enforce kind='proceeding'. m's call on the 11 AskUserQuestion decisions: - Model 1 (kind discriminator) - Phases implicit via procedural_events.event_kind, EXCEPT upc.costs.cfi stays kind='proceeding' (standalone R.151 application) - Side-actions: kind='side_action', rules anchor on parent primary - Schutzschrift kind='proceeding' (own RoP filing) - DE inf + DE null + DE-vs-upc.apl unification: all keep discrete - upc.ccr.cfi: keep status quo per t-paliad-204 S1 - DB trigger on projects only (admin-only writes on sequencing_rules) - Deactivate non-primary rows (23 active post-mig, all kind='proceeding') - Parallel-land vs m/paliad#146 — knuth's S3 picks up the filter Final categorisation: 23 proceeding / 4 phase / 10 side_action / 9 meta. No code yet — coder gate held per inventor SKILL. Design only. Closes the inventor pass on m/paliad#147. |
|||
| 081b66ebc8 |
Merge: t-paliad-323 Slice S2 — Fristenrechner result view under ?overhaul=1 (m/paliad#146)
knuth shipped S2 of the Fristenrechner overhaul (design §4, §7-S2):
- New frontend/src/client/fristenrechner-result.ts (611 LoC) — renders the shared result view: trigger card (sticky header, inline date editor), 4 priority groups (Mandatory / Recommended / Optional / Conditional) with SPAWNED badge per §4.2, per-rule rows with checkbox + inline date override + party/citation badges, write-back footer conditional on project!=null (§11.Q7 — kontextfrei mode shows informational nudge instead).
- 72-LoC test suite covers groupFollowUps + defaultChecked semantics.
- Page wiring: ?overhaul=1 query param mounts the result view in place of the legacy renderProcedureResults; both coexist this slice. Deep-link shape: ?overhaul=1&event=<code>&trigger_date=…&project=… per §5.
- audit_reason wording in the bulk write-back call: 'Aus Fristenrechner — Trigger: {name} ({date})' per §11.Q12.
- 340 LoC of new CSS (entity-table extensions, group dividers, badge tokens).
- bun build clean; 249 existing frontend tests + 9 new pass; go build + vet clean; S1 live-DB tests still green.
PAUSED AT SEAM — knuth parked persistent. S3+ (Mode A/B wizard chips) waits for the proceeding_types taxonomy redesign (m/paliad#147, atlas in flight) to ratify the qualifier set that R3 picks from.
|
|||
| 9ab8dd8e0f |
feat(fristenrechner): Slice S2 — result view under ?overhaul=1 (m/paliad#146)
New `frontend/src/client/fristenrechner-result.ts` module renders the
shared result surface defined in
docs/design-fristenrechner-overhaul-2026-05-26.md §4:
* Sticky trigger card — event icon + name, proceeding/jurisdiction
chips, inline trigger-date input that re-fetches on change.
* Four follow-up groups — Mandatory / Recommended / Optional /
Conditional. SPAWNED rules fold into their priority bucket with
a `⇲ neues Verfahren` badge (§11.Q5). Conditional bucket holds
every rule with sr.condition_expr IS NOT NULL.
* Per-rule rows — title, duration phrase, party chip, legal-source
citation (with youpc.org link when available), pre-checked
checkbox driven by `defaultChecked(r)` (mandatory + recommended
on; conditional + court-set + optional off), inline ✏ Datum
override that re-renders.
* Write-back footer — conditional on `?project=<uuid>` per §11.Q7;
in kontextfrei mode the footer is hidden and an inline nudge
invites the user to pick an Akte. CTA submits to the existing
POST /api/projects/{id}/deadlines/bulk endpoint, stamping each
row with `audit_reason: "Aus Fristenrechner — Trigger: {name}
({date})"` per §11.Q12.
Mount + URL contract — when `?overhaul=1` is set in the URL,
`fristenrechner.ts` hides every legacy panel (`fristen-step1`,
`fristen-step2`, `fristen-pathway-a`, `fristen-pathway-b`,
`fristen-step3a`, the step-1 summary) and shows the overhaul root
instead. With `?overhaul=1&event=<code>&trigger_date=…` the surface
is deep-linkable end-to-end. Without `?event=` the empty-shell
nudge renders — S3+S4 will mount the entry-mode UIs onto this same
root.
Verified — bun build clean, 249 frontend tests pass (incl. 9 new
helper tests for groupFollowUps + defaultChecked), go build + vet
clean, S1 live-DB tests still green.
|
|||
| 4218d9cb52 |
Merge: t-paliad-323 Slice S1 — Fristenrechner backend endpoints (m/paliad#146)
knuth shipped S1 of the Fristenrechner overhaul (docs/design-fristenrechner-overhaul-2026-05-26.md §6, §7-S1): - GET /api/tools/fristenrechner/search?kind=events — returns procedural_events tuples with trigram ranking + follow-up counts (alongside the existing concept-card response). New service: services/fristenrechner_search_events.go (257 LoC). - GET /api/tools/fristenrechner/follow-ups — given trigger event + date + optional party qualifier, returns sequencing_rules anchored on the event with computed due dates via pkg/litigationplanner.CalculateRule. New service: services/fristenrechner_followups.go (404 LoC). - 6 live-DB integration tests (services/fristenrechner_followups_test.go, 205 LoC): SoC follow-ups, party narrowing, jurisdiction filters, event_kind filters, unknown-event sentinel. No schema changes — the unified sequencing_rules model already has every column needed. Knuth proceeds to S2 (result view under ?overhaul=1). |
|||
| 7ea415145f |
feat(fristenrechner): Slice S1 — backend ?kind=events + /follow-ups (m/paliad#146)
Two additive endpoints behind the Fristenrechner overhaul (design §6.1 + §6.2 in docs/design-fristenrechner-overhaul-2026-05-26.md): 1. GET /api/tools/fristenrechner/search?kind=events — returns procedural_events rows directly (not aggregated concept-cards), one hit per (event × proceeding_type) tuple. Trigram-ranked against name / name_en / code. Filters: jurisdiction, proc, event_kind, party. Powers Mode A's result list and Mode B's R4 landing chips. Default search shape unchanged. 2. GET /api/tools/fristenrechner/follow-ups?event=...&trigger_date=... — given a trigger event (by code or uuid) + date, returns the immediate follow-up sequencing rules with computed due dates via litigationplanner.CalculateRule. Each row carries priority / primary_party / is_court_set / is_spawn / has_condition / legal source / spawn target so the result view can group into Mandatory / Recommended / Optional / Conditional with the SPAWNED badge. party=claimant|defendant filters keep "both" rules visible. No schema changes — unified sequencing_rules already has every column needed. Live-DB tests cover the SoC follow-up shape, party narrowing, jurisdiction + event_kind filters, and the unknown- event sentinel. |
|||
| 109946edff |
Merge: t-paliad-322 — Fristenrechner overhaul design doc (docs only) (m/paliad#146)
553-line design doc documenting the complete Fristenrechner UX overhaul. Coder shift gated on m's go/no-go.
Two complementary entry paths into a shared result view:
- Mode A 'Direkt suchen' — search + filter chips (Forum · Proceeding · Event-Kind · Partei), result list of procedural_events, click locks a trigger.
- Mode B 'Geführt' — 3-5 row wizard (R1 event_kind → R2 forum → R3 proceeding_type → R4 procedural_event → R5 perspective), pre-filling + auto-skip from project context, row badges marking Filter vs Qualifier.
Shared result view groups follow-up sequencing_rules by Mandatory / Recommended / Optional / Conditional (SPAWNED folded with a 'neues Verfahren' badge). Trigger card sticks with inline-editable trigger date. Write-back via POST /api/projects/{id}/deadlines/bulk through a confirm-and-edit-dates modal. Kontextfrei mode hides the CTA entirely (m §11.Q7).
Filter vs Qualifier axis taxonomy ratified:
- forum, event_kind: filters
- proceeding_type, perspective (in file mode), procedural_event: qualifiers
- inbox channel: dropped from primary surface, kept as Mode A secondary chip
Backend deltas: extend /search to return events; new /follow-ups endpoint. No schema changes — the unified sequencing_rules model already has every column needed.
6-slice migration: S1 backend handlers → S2 result view (?overhaul=1) → S3 Mode A → S4 Mode B → S5 flip flag default → S6 drop buildRowStack + cascade reads. Procedure-mode (upper half of fristenrechner.tsx) untouched.
All 12 PRD questions ratified by m on 2026-05-26 via AskUserQuestion. 10/12 matched inventor recommendation; 2 diverged (Q3 section-split UX, Q7 hide kontextfrei CTA). Per-pick reasoning + design impact in §11.
Cronus parked on mai/cronus/inventor-fristenrechner. Coder shift held pending m's go.
|
|||
| 528fe35540 |
design(fristen): fold m's 12 decisions into Fristenrechner overhaul doc
All 12 questions answered via AskUserQuestion. 10/12 = inventor recommendation.
2 diverged:
Q3 (Filter-vs-qualifier UX): m picked section-split (Filter strip above,
result/qualifier strip below) instead of '(Pflichtangabe)' tag.
§3.1 Mode A layout rewritten with Filter strip header; §3.2 wizard
rows now carry Filter/Qualifier badges next to the row number.
Q7 (No-project mode): m picked 'Hide CTA entirely' instead of disabled-
with-hint. §4.4 footer renders only when project != null; an inline
'Tipp: Wähle oben eine Akte' nudge replaces the missing footer.
New §11 'm's decisions (2026-05-26)' anchors each pick with reasoning where
it diverges from the recommendation. §11.1 captures the two follow-on edits
to §3.1 and §4.4. Migration plan and backend contracts unchanged.
DESIGN READY FOR REVIEW pending head's coder gate.
|
|||
| 9c2788ed8c |
design: Fristenrechner complete UX overhaul (t-paliad-322)
Inventor shift-1 design pass for m/paliad#146. - Mode taxonomy (Direct-search A + Wizard B → shared result view) - Filter-vs-qualifier table ratified (forum/event_kind/inbox as filters; proceeding_type/perspective as qualifiers) - Wizard branching: R1 event_kind → R2 forum → R3 proceeding_type → R4 procedural_event → R5 perspective; rows prefill+collapse from project - Result view: 4 priority groups (mandatory/recommended/optional/conditional) with SPAWNED folded into priority + cross-proceeding badge - Project write-back via existing POST /api/projects/{id}/deadlines/bulk with confirm-and-edit-dates modal and audit_reason wording - Backend deltas: extend /api/tools/fristenrechner/search to return procedural_events; new /api/tools/fristenrechner/follow-ups - No schema changes — pure UX + handler shape - 6-slice migration plan from current buildRowStack to overhaul under ?overhaul=1 flag, then flip + cleanup - One worked example (LG Düsseldorf Hinweisbeschluss) - 12 open questions for m (3 batches of 4 via AskUserQuestion) |
|||
| c56859058d |
Merge: t-paliad-321 — mig 152 dedupe identical sequencing_rule clones + Proceeding column on admin list (m/paliad#144 follow-up)
mig 151 archived 5 of 6 duplicate procedural_events for 'Mängelbeseitigung / Zahlung' and reparented their sequencing_rules. The 6 sequencing_rules themselves were byte-for-byte clones (NULL proceeding/rule_code, 14d duration) — admin showed 6 indistinguishable rows for one legal concept.
Mig 152: full-signature partition over sequencing_rules, lowest UUID per group as canonical, archive the rest. Audit-first RAISE NOTICE pre-block surfaces every clone-group in deploy logs. Snapshot to paliad.sequencing_rules_pre_152. Reparents deadlines.sequencing_rule_id (renamed from rule_id in mig 140). Defensive set_config('paliad.audit_reason') even though sequencing_rules has no audit trigger live.
Expected outcome: 5 archived (just Mängelbeseitigung / Zahlung). Other name-groups (Antrag auf Patentänderung×4, Beginn des Hauptsacheverfahrens×2, Berufungs*-R.220.1×2) have distinct (proceeding_type_id, rule_code, duration, primary_party) signatures — legitimately different rules per proceeding, left alone.
UI: admin-rules-list gains a Proceeding column (proceeding_type.code, server-side join). Replaces the legacy Verfahrenstyp column which was broken for non-fristenrechner categories. One column for proceeding info instead of two; works for every category.
Build + vet clean. NoDuplicateSlot passes.
|
|||
| 6acb1167dd |
feat(admin): add proceeding-type column to /admin/procedural-events list (t-paliad-321 / m/paliad#144)
Surfaces the 3-segment proceeding code (e.g. upc.inf.cfi) on the admin
rules list so the 4 legitimately-distinct same-named groups are
visually disambiguated without opening each row's edit page.
Specifically helps with:
- "Antrag auf Patentänderung" × 4 (distinct proceeding_type_ids)
- "Beginn des Hauptsacheverfahrens" × 2
- "Berufungsbegründung-R.220.1" × 2
- "Berufungsschrift-R.220.1" × 2
(The 6× "Mängelbeseitigung / Zahlung" identical clones are dedup'd by
mig 152 in the sibling commit; this column lets m verify the dedupe
landed and confirms the remaining same-named groups are intentional.)
* internal/services/rule_editor_service.go —
- LoadProceedingTypeCodes(ctx, rows) — batch SELECT id, code FROM
paliad.proceeding_types WHERE id = ANY(...) for every distinct
non-NULL proceeding_type_id in rows. Returns id → code map.
Single round-trip, firm-wide reference data (no RLS / visibility
gate). Used only by the LIST endpoint; GetByID etc. don't need it.
* internal/handlers/admin_rules.go —
- adminRuleResponse gains ProceedingTypeCode *string field
(json:"proceeding_type_code,omitempty"). Populated by
wrapRuleListResponse from the id → code map.
- handleAdminListRules calls LoadProceedingTypeCodes after fetching
rows, passes the map to wrapRuleListResponse.
* frontend/src/admin-rules-list.tsx —
- Adds Proceeding column header in position 2 (between Submission
Code and Legal Citation) per paliadin's "Place between submission-
code and the existing columns" spec. Binds to canonical i18n
key admin.procedural_events.col.proceeding (added below).
- Drops the legacy Verfahrenstyp column at position 4 — the new
code-only column at position 2 replaces it; the old column
showed `code · name` which duplicates the new content.
* frontend/src/client/admin-rules-list.ts —
- Rule type gains proceeding_type_code?: string | null.
- New proceedingCodeCell(r) helper: prefers server-side
proceeding_type_code, falls back to dropdown-lookup
proceedingLabel for defense-in-depth on older API responses
(the old behaviour broke for rules whose proceeding_type_id
pointed at non-fristenrechner category proceedings; the new
column never has that bug because the join is server-side).
- Row rendering: new <td class="admin-rules-col-proceeding"><code>
proceedingCodeCell(r) </code></td> in column 2.
* frontend/src/client/i18n.ts —
- admin.procedural_events.col.proceeding alias added for DE +
EN ("Verfahren" / "Proceeding"). Mirror style of the other
canonical aliases from Slice A.
* frontend/src/i18n-keys.ts —
- Generated key union extended with
"admin.procedural_events.col.proceeding".
Build + vet clean. No new SQL — proceeding_types is firm-wide
reference data and the join uses an existing primary key.
|
|||
| 4cd28bc896 |
feat(db): mig 152 — dedupe identical sequencing_rule clones (5 archived) (t-paliad-321 / m/paliad#144 follow-up)
Mig 151 (t-paliad-319) archived 5 of 6 duplicate procedural_events for
"Mängelbeseitigung / Zahlung" and reparented their sequencing_rules
onto the canonical PE. The 6 sequencing_rules themselves were left
active — and they are byte-for-byte clones (proceeding_type_id=NULL,
rule_code=NULL, duration 14d, primary_party=NULL, condition_expr=NULL,
…). The admin shows six indistinguishable rows for one legal concept.
This migration archives 5 of 6, keeping the row with the
lexicographically lowest UUID as canonical.
Pre-write verification (Supabase MCP, 2026-05-26):
- Exactly 1 clone-group surfaces under the full-signature query
(procedural_event_id, proceeding_type_id, rule_code, duration_*,
primary_party, condition_expr::text, trigger_event_id, alt_*,
anchor_alt, combine_op, parent_id, is_spawn, spawn_*):
6 "Mängelbeseitigung / Zahlung" rows.
- 0 paliad.deadlines reference any of the 5 to-be-archived rows
(verified via deadlines.sequencing_rule_id JOIN; rule_id column
was dropped in mig 140 / Slice B.4).
- Other name-duplicates (Antrag auf Patentänderung×4, Beginn des
Hauptsacheverfahrens×2, Berufungsbegründung-R.220.1×2,
Berufungsschrift-R.220.1×2) do NOT collapse under this signature —
their proceeding_type_id / rule_code / duration / primary_party
differ. Legitimately distinct rules per proceeding. This mig
leaves them alone.
Migration shape (mirrors mig 151):
1. Build dedupe mapping (duplicate_id → canonical_id) into a
ROW_NUMBER() OVER (PARTITION BY full-signature ORDER BY
created_at, id::text) TEMP table.
2. PRE NOTICE: surface every clone-group with its canonical + dups
so the deploy log shows what's about to be touched (m may want
to spot-check).
3. Snapshot the duplicates into paliad.sequencing_rules_pre_152
(precedent pre_091/093/095/098/140/151).
4. Reparent paliad.deadlines.sequencing_rule_id duplicate → canonical
BEFORE archiving (defensive no-op today).
5. set_config('paliad.audit_reason', …) — defensive; sequencing_rules
has no audit trigger yet (mig 151 §scope verified), but a future
trigger would inherit the reason automatically.
6. UPDATE sequencing_rules SET is_active=false,
lifecycle_state='archived' WHERE id IN dups.
7. POST assertions: expected archive count met, zero clone groups
remaining in active+published, zero live deadlines pointing at
an archived sequencing_rule. RAISE EXCEPTION on any mismatch.
Down: best-effort revert (flips archived → published from snapshot).
Doesn't undo the deadlines reparent (live data didn't need one;
snapshot doesn't carry pre-state of deadlines).
Build + vet clean. TestMigrations_NoDuplicateSlot passes.
|
|||
| 568eac0aff |
Merge: t-paliad-320 — editorial seed cmd for 5 orphan deadline_concept drafts (4 concepts) (m/paliad#193)
darwin (researcher + /mai-lexy) staged 5 lifecycle_state='draft' sequencing_rules via services.RuleEditorService.Create() for the 4 remaining orphan deadline_concepts: - counterclaim-for-revocation → upc.ccr.cfi, RoP.025, 3 months (32aafb64) - versaeumnisurteil-einspruch → de.inf.lg, § 339 ZPO, 2 weeks Notfrist (eda1756a) - schriftsatznachreichung → de.inf.lg, § 283 ZPO, 3 weeks court-set (08b1682a) - weiterbehandlung (EPC) → epa.grant.exa, Art. 121 EPÜ + R. 135(1), 2 months (73674564) - weiterbehandlung (DPatG) → event-rooted (NULL proc), § 123a PatG, 1 month (16e262d2) Deliverable: cmd/seed-orphan-concept-drafts/main.go — runs against RuleEditorService in-process; idempotent; audit-reason flag. Editorial follow-up flagged in DPatG rule's deadline_notes: no dpma.grant.* proceeding_type exists yet; create dpma.grant.dpma and reassign rule 16e262d2 once added. Drafts ready for m's editorial review at /admin/procedural-events. |
|||
| 733d21c930 |
feat(seed): editorial cmd to stage drafts for orphan deadline_concepts (t-paliad-320)
Stages five lifecycle_state='draft' sequencing_rules — one per orphan deadline_concept — via services.RuleEditorService.Create(), the same service the POST /admin/api/procedural-events handler hits internally (audit trigger + INSTEAD-OF view trigger fan-out into procedural_events + sequencing_rules + legal_sources). No HTTP/auth shell, no raw SQL writes. Drafts (slug → proceeding): - counterclaim-for-revocation → upc.ccr.cfi, 3 months, RoP.025 - versaeumnisurteil-einspruch → de.inf.lg, 2 weeks Notfrist, § 339 ZPO - schriftsatznachreichung → de.inf.lg, 3 weeks court-set, § 283 ZPO - weiterbehandlung (EPC) → epa.grant.exa, 2 months, Art. 121 EPÜ + R. 135(1) EPÜ - weiterbehandlung (DPatG § 123a) → event-rooted (NULL proc), 1 month The DPatG variant is event-rooted because no dpma.grant.* proceeding_type exists yet — flagged in deadline_notes as editorial follow-up. Idempotent: refuses to insert if (concept, proceeding, rule_code) already exists. |
|||
| b05bcf7eeb | Merge: t-paliad-319 — mig 151 dedupe null.* procedural_events (9 archived, 5 name-groups consolidated) (m/paliad#144) | |||
| 71e8023784 |
feat(db): mig 151 — dedupe null.* procedural_events (t-paliad-319 / m/paliad#144)
Consolidates 5 name-groups with synthetic null.<8hex> codes (minted by mig 136 from legacy submission_code IS NULL rows) onto a single canonical PE per name. 9 duplicate rows archived (is_active=false, lifecycle_state='archived'), 9 sequencing_rules reparented onto their canonical procedural_event. Worst offender: "Mängelbeseitigung / Zahlung" 6 → 1. Audit-first: per-row RAISE NOTICE before the writes, plus snapshots in paliad.procedural_events_pre_151 and paliad.sequencing_rules_pre_151 (same TX, mirrors precedent pre_091/093/095/098/140). Post-asserts that no name-group still has >1 active+published null.* row and no sr points at an archived PE. Pre-flight schema audit confirmed no audit trigger on procedural_events or sequencing_rules (only INSTEAD OF triggers on deadline_rules_unified, which don't fire on direct table writes), 0 deadlines + 0 draft_of refs to the duplicates, and lifecycle_state has no CHECK constraint blocking 'archived'. .down.sql best-effort restores sr.procedural_event_id and reactivates the archived rows from the snapshot tables. Mig already applied to youpc paliad schema via Supabase MCP within the same TX as the applied_migrations row insert (checksum matches the embedded file); deployed binary will see version 151 as applied. |
|||
| d190fbe0a4 | Merge: hotfix #3 mig 140 — filter POST check to active+published (B.2 dual-write scope) | |||
| e0a82d9f9e |
fix(mig 140): post-check filters to active+published rows only
The previous post-check compared unfiltered counts (snapshot 493 vs sequencing_rules 231) and false-positived as "dual-write drift". Reality: B.2 dual-write was scoped to is_active=true + lifecycle_state='published' (the read-path universe). Archived + draft rows in deadline_rules were never replicated to sequencing_rules because nothing read them. Patch: filter both counts to active+published before comparison — the invariant B.2 actually maintained. Archived/draft rows survive in deadline_rules_pre_140 for forensic / future-backfill. Third hotfix on mig 140 today (1: missing matview drop; 2: wrong post-check comparand; 3: post-check missing lifecycle filter). The slice itself is sound — every failure was in the verification path, not the data. |
|||
| d326f9aa4a | Merge: hotfix mig 140 — POST check compares snapshot to sequencing_rules (was view) (m/paliad#93 hotfix #2) | |||
| 026ad2d5ee |
fix(mig 140): POST integrity check compares snapshot to sequencing_rules, not view
The previous post-check compared paliad.deadline_rules_pre_140 row count to paliad.deadline_rules_unified row count and failed with "snapshot has 493 rows, view has 231 rows — drift". That's a false positive: the snapshot has every row (all lifecycle states + is_active), the view filters to is_active+published. They're not supposed to match. The right invariant: snapshot row count == sequencing_rules row count (B.2 dual-write keeps them 1:1 across all lifecycle states). Patched. View count stays in the RAISE NOTICE line as informational. Refs t-paliad-305 / m/paliad#93 Slice B.4 hotfix #2. |
|||
| 13a65a6d6e | Merge: Composer Slice F — section reorder/hide/add custom. Composer A→F complete (m/paliad#141) | |||
| bd7896ef68 |
feat(submissions): Composer Slice F — section reorder / hide / add custom (m/paliad#141)
The final Composer slice per design doc §12. Lawyer gains full
control over section composition: drag-and-drop reorder, per-section
delete, "+ Add section" picker for custom slugs that don't appear in
the base's default spec. Combined with Slice B's hide toggle, this
closes out the A→F sequence — Composer A→F is complete.
Backend (internal/services/submission_section_service.go, +120 LoC):
- SectionService.Create — adds a new section row to a draft. Validates
section_key + labels + kind (must be prose/requests/evidence).
Auto-assigns next order_index when OrderIndex=0; collisions on
(draft_id, section_key) surface as ErrInvalidInput.
- SectionService.Delete — removes one section by id. Returns
ErrSubmissionSectionNotFound when nothing was deleted.
- SectionService.Reorder — accepts a sequence of section_ids, rewrites
every row's order_index to (1..N)×10 transactionally. Returns the
refreshed list. Sections not present in the sequence are silently
ignored (defensive — partial reorder doesn't lose rows).
Handlers (internal/handlers/submission_sections.go, +180 LoC):
- POST /api/submission-drafts/{draft_id}/sections — owner-scoped via
SubmissionDraftService.Get. 400 on slug collision / invalid kind.
- DELETE /api/submission-drafts/{draft_id}/sections/{section_id} —
owner + section-belongs-to-draft cross-check. 204 on success.
- POST /api/submission-drafts/{draft_id}/sections/reorder — accepts
{"section_order": [uuid, uuid, ...]}; returns refreshed sections list.
Frontend (frontend/src/client/submission-draft.ts, +260 LoC):
- Each section row gains a drag handle (⋮⋮) on the left of the head.
Drag handle is the only draggable element; contentEditable
selections inside the editor body keep working. HTML5 native DnD,
no library.
- Drop-target highlighting via .submission-draft-section--drop-target
(border-top accent). Cleanup on dragend / drop / cancel.
- Per-section "Delete" button next to the existing Hide/Include
toggle. Confirm prompt prevents accidental loss of typed prose.
- "+ Add section" trailing affordance below the section list opens an
inline form (slug + DE label + EN label + kind dropdown). Submit
POSTs to the new endpoint; on success splices the row into
state.view.sections and re-paints.
CSS (frontend/src/styles/global.css, +65 LoC):
- .submission-draft-section-handle (grab cursor + hover background +
active=grabbing).
- .submission-draft-section--dragging / --drop-target visual states.
- .submission-draft-add-section form layout (dashed border + lime
primary submit).
Tests (internal/services/submission_section_slice_f_test.go, NEW,
TEST_DATABASE_URL-gated):
- Create custom section + slug-collision surface as ErrInvalidInput.
- Delete + repeat-delete returns ErrSubmissionSectionNotFound.
- Reorder reverses 10 seeded sections + verifies the resulting
order_index sequence is ascending and matches the input order.
Build hygiene: go build/vet/test -short clean (all packages);
bun run build clean (2906 i18n keys, data-i18n scan clean).
Hard rules honoured:
- NO new migrations (Slice F is pure code on Slice A's schema).
- NO behavior change for pre-Composer drafts (no section rows → no
drag handles to drag).
- {{rule.X}} aliases preserved (custom sections render through the
same composer pipeline as default sections).
- Q2/Q9/Q10 ratifications preserved.
This closes the Composer slice sequence A → F. The full feature set
ratified by m on 2026-05-26 is now in place:
A — base picker + read-only section list (mig 146/147/148)
B — editable prose + anchor-spliced render + MD→OOXML walker
C — building-blocks library + section picker (mig 149)
D — rich prose (headings, lists, blockquote, hyperlinks)
E — specialist bases lg-duesseldorf + upc-formal (mig 150)
F — section reorder / delete / add custom
t-paliad-318 Slice F
|
|||
| 946f373651 | Merge: Composer Slice E — specialist bases lg-duesseldorf + upc-formal (mig 150) + base-swap content survival (m/paliad#141) | |||
| 94310ba498 |
feat(submissions): Composer Slice E — specialist bases + base-swap content survival (m/paliad#141)
Two new firm-agnostic base templates + the generic generator that
produced them + a regression test pinning Q10's base-swap-content-
survival contract.
Mig 150: seeds two `submission_bases` rows with firm=NULL.
- lg-duesseldorf — proceeding_family='de.inf.lg'. Conservative
German legal style: Times New Roman 11pt; plain black headings.
Stylemap targets LG-Body / LG-Heading1..3 / LG-ListBullet /
LG-ListNumber / LG-Quote.
- upc-formal — proceeding_family='upc.inf.cfi'. UPC court style:
Calibri 11pt body; UPC-blue (#1F3864) headings; Cambria italic
for blockquotes. Stylemap targets UPC-Body / UPC-Heading1..3 / …
Both rows ship the same 10-section spec.defaults shape as the Slice A
bases (letterhead → signature) with their own seed Markdown.
scripts/gen-submission-base/main.go (NEW, ~240 LoC):
- Generic generator with -preset flag. Two presets baked in
(lg-duesseldorf + upc-formal). Each preset hard-codes typography
(font, sizes, colour) so the lawyer can swap between bases and
see chrome change while section content carries through unchanged.
- Output is byte-reproducible (zip mtime pinned to 2026-05-26 UTC).
- Emits a minimal Composer-mode .docx: [Content_Types].xml,
_rels/.rels, word/_rels/document.xml.rels (empty envelope so the
composer's hyperlink-rels patch from Slice D has somewhere to land),
word/styles.xml (preset's full named-style block + "Hyperlink"
character style for Slice D link runs), word/document.xml (anchor-
only body in §6.1 default section order).
Gitea uploads (via mAi):
- 6 - material/Templates/Word/Paliad/Composer/lg-duesseldorf.docx
blob SHA: 82f57b3cb3b54c755fc5ab36862bfd61b8aaa73e
- 6 - material/Templates/Word/Paliad/Composer/upc-formal.docx
blob SHA: 41b9a388263ccc43ddc28b55caab301a4cf74fe8
These live under Composer/ (not under HLC/) so a future non-HLC
deployment serves the same cross-firm files.
Backend wiring:
- internal/handlers/files.go: two new fileRegistry entries
(composerBaseLGDuesseldorfSlug, composerBaseUPCFormalSlug) +
matching slugs in composerBaseSlugMap so fetchComposerBaseBytes
routes the new catalog rows to the new Gitea objects.
Tests:
- TestComposer_BaseSwapPreservesContent — composes the same draft
against an HLC-style stylemap AND an LG-style stylemap; asserts
(a) content survives both ways, (b) each output carries the
correct stylemap-entry stylenames, (c) neither output leaks the
other's stylenames. Pins Q10's base-swap-survives-content
contract.
Build hygiene: go build/vet/test -short clean (all packages);
bun run build clean.
NOT in scope (Slice E's brief was specialist bases + survival test):
- Generator coverage for HL Patents Style bases — gen-hl-skeleton-
template stays as the per-firm path (it needs the proprietary
.dotm source). gen-submission-base is for firm-agnostic bases.
- LG-Düsseldorf-court-style-guide deep fidelity — the LG preset is
a conservative starting point; admin refines via the admin editor
in a later slice if needed.
- numbering.xml carrying numId=1/2 — Slice D's MD walker emits
visible "• " / "N. " prefixes that don't need numbering.xml;
honours stylemap entry for indentation.
Hard rules honoured:
- Migration purely additive (`ON CONFLICT (slug) DO NOTHING`).
- NO behavior change for pre-Composer drafts.
- NO behavior change for existing hlc-letterhead + neutral seed
rows.
- {{rule.X}} aliases preserved (walker passes placeholders through;
v1 SubmissionRenderer pass substitutes).
- Q10 base-swap-content-survival pinned by new test.
t-paliad-317 Slice E
|
|||
| 5834e3dc66 | Merge: Composer Slice D — rich prose (headings, lists, blockquote, hyperlinks) in MD→OOXML walker (m/paliad#141) | |||
| 677849784c |
feat(submissions): Composer Slice D — rich prose (headings, lists, blockquote, hyperlinks) (m/paliad#141)
Extends the Composer's MD → OOXML walker per the design at
docs/design-submission-generator-v2-2026-05-26.md §12 Slice D from
Slice B's paragraphs + B/I baseline to the full rich-prose feature set:
headings 1-3, bullet + numbered lists, blockquote, inline hyperlinks.
MD walker (internal/services/submission_md.go, +320 / -75 LoC):
- RenderMarkdownToOOXMLWithStyles is the new Slice-D entry point;
RenderMarkdownToOOXML stays as a thin back-compat wrapper.
- splitMarkdownBlocks classifies every line into one of:
paragraph, heading_1/2/3, list_bullet, list_numbered, blockquote.
CommonMark-style 3-space indent tolerance; "N. " and "N) " for
numbered. Blank-line spacing semantics preserved from Slice B.
- renderBlockParagraph applies stylemap[blk.styleKey] (with
fall-back to stylemap["paragraph"]). List blocks emit visible
"• " / "N. " prefix runs so the structure surfaces even if Word
isn't configured with auto-list-numbering — lawyer can apply a
real Word list style post-export. Numbered-list ordinals reset
on every non-list block (so "1. A\nplain\n1. C" renders 1./1.,
not 1./2.).
- parseInlineRuns adds `[label](url)` recognition. Each link gets
routed through the optional HyperlinkAllocator; the walker emits
`<w:hyperlink r:id="{rId}">…runs…</w:hyperlink>` with the
"Hyperlink" character style on each child run. Nil allocator
falls back to plain-text label (URL drops, label survives).
Composer (internal/services/submission_compose.go, +130 / -10 LoC):
- composerLinkAllocator hands the walker fresh rIds (rIdComposer1,
rIdComposer2, …) outside the base's existing namespace; same URL
shared across multiple sections dedupes to one rId.
- patchDocumentXMLRels appends matching <Relationship Type="…/hyperlink"
Target="URL" TargetMode="External"/> entries to
word/_rels/document.xml.rels. Idempotent on rIds already present;
synthesizes a fresh rels part when missing (defensive for stripped
bases). Returns the patched parts slice (caller must overwrite
because append may grow the backing array — fixed in this slice).
- Compose now passes the full stylemap (paragraph + heading_1/2/3 +
list_bullet + list_numbered + blockquote) into the walker, not
just the paragraph-style entry.
Frontend (frontend/src/client/submission-draft.ts):
- Toolbar adds H1/H2/H3 buttons (formatBlock h1/h2/h3), bullet
list, numbered list, blockquote, and a link button that prompts
for a URL + wraps the selection via execCommand("createLink").
- domToMarkdown serializer extends to <h1>/<h2>/<h3>, <ul>/<ol>
with per-item ordinal counter for numbered lists, <blockquote>,
and <a href="…"> → `[label](url)`. Nested <li> handling sits in
the ul/ol branch.
Tests (internal/services/submission_md_test.go, internal/services/
submission_compose_test.go):
- TestRenderMarkdownToOOXML_Heading1 / _Heading2And3 — stylemap
applied.
- _BulletList / _NumberedList / _NumberedListResetsOnNonList —
prefixes + ordinal counter.
- _Blockquote — stylemap applied.
- _Hyperlink — allocator called, w:hyperlink rId wired, Hyperlink
character style on label runs.
- _HyperlinkNilAllocatorFallsBackToPlain — label survives, no
hyperlink tag emitted.
- TestDetectBlockMarker — 13 marker / non-marker cases.
- TestComposer_HeadingsAndLists — end-to-end through Compose with
a multi-construct draft; verifies stylemap presence + content +
ordinal prefixes.
- TestComposer_HyperlinkWiresRels — body has the right
<w:hyperlink r:id="rIdComposer{N}">, document.xml.rels has the
matching <Relationship> rows with External target mode.
- TestComposer_HyperlinkDedupesByURL — two `[label](url)` references
to the same URL share one rId; second allocation gets no new
Relationship row.
Build hygiene: go build/vet/test -short clean (all packages); bun run
build clean (2906 i18n keys).
NOT in scope (Slice D's brief was rich-prose + toolbar):
- Numbering.xml audit on bases — current approach emits visible
"• " / "N. " prefix runs without depending on numbering.xml. A
future slice can swap to `<w:numPr>` if firm-style auto-numbering
becomes a hard requirement.
- DOM-from-Markdown on initial editor paint — the editor still uses
textContent=md, so toolbar-applied formatting reverts to literal
Markdown text after autosave + repaint. Acceptable trade-off for
Slice D; a future polish could parse MD into the DOM on paint.
- Tables, images, footnotes (still design §13 out of scope).
Hard rules honoured:
- NO new migrations (Slice D is pure code).
- NO behavior change for pre-Composer drafts (gate on draft.BaseID
unchanged).
- {{rule.X}} aliases preserved (placeholders pass through the walker
verbatim, get substituted by the v1 SubmissionRenderer pass).
- Q2 ratification preserved (no building_block_id lineage).
- Q9 ratification preserved (4-tier BB visibility from Slice C).
t-paliad-316 Slice D
|
|||
| b27d402156 | Merge: Slice B.6 — /admin/rules → /admin/procedural-events URL rename + 301 redirects + .tsx i18n rebind. #93 slice train concludes (m/paliad#93) | |||
| 14290294b4 | Merge: hotfix mig 140 — drop+recreate deadline_search matview (unblock prod) | |||
| 6b970da774 |
fix(mig 140): drop+recreate deadline_search matview (was blocking DROP TABLE deadline_rules)
prod-down: mig 140 fails with `cannot drop table deadline_rules because other objects depend on it (2BP01)`. The dependent object is the deadline_search materialized view (mig 077) — curie's brief listed FK re-pointing but missed the matview. Fix: drop the matview before DROP TABLE deadline_rules, recreate it at the end of mig 140 against deadline_rules_unified (same column shape). All 11 indexes restored. REFRESH at end so search keeps working. Single-TX atomicity preserved — if anything past step 6a fails, the whole drop-and-recreate rolls back. The pre_140 snapshot from step 1 remains as the forensic backstop. Refs t-paliad-305 / m/paliad#93 Slice B.4. |
|||
| 9359e99a6b |
feat(handlers,frontend): Slice B.6 — admin URL rename /admin/rules → /admin/procedural-events with 301 redirects + .tsx i18n rebind (t-paliad-305 / m/paliad#93)
Closes the procedural-events rename loop opened by m/paliad#93. The admin surface now lives under its canonical URL; the legacy paths remain reachable for one deprecation cycle via 301 redirects so bookmarks, audit-log entries, and curl scripts keep working. * internal/handlers/handlers.go — - Registers the 12 canonical routes under /admin/procedural-events* (page paths and JSON API). Same handlers — just the new URL slot. - Registers the 12 legacy /admin/rules* routes as 301 redirects. * internal/handlers/admin_rules.go — - redirectToProceduralEvents(dst) — fixed-destination redirect for paths without an {id}. - redirectToProceduralEventEdit — page redirect carrying the {id}. - redirectToProceduralEventAPI(suffix) — JSON API redirect carrying {id} + optional suffix (/clone-as-draft, /publish, /archive, /restore, /audit, /preview). Query string is preserved on every redirect. - All three helpers add the IETF Deprecation header + a Link header pointing at the successor-version path. * frontend internal nav + URL strings — Sidebar.tsx, admin.tsx, admin-rules-list.tsx, admin-rules-edit.tsx, client/admin-rules-list.ts, client/admin-rules-edit.ts: every `/admin/rules*` reference flipped to `/admin/procedural-events*`. In-app navigation now hits the canonical paths directly without a redirect round-trip; external callers keep working via the 301s. * frontend .tsx i18n rebind — 9 admin .tsx i18n bindings rebound to the canonical `admin.procedural_events.*` keys that already exist as aliases in i18n.ts (per Slice A from t-paliad-262). Specifically: admin.rules.list.title → admin.procedural_events.list.title admin.rules.list.heading → admin.procedural_events.list.heading admin.rules.list.new → admin.procedural_events.list.new admin.rules.col.submission_code → admin.procedural_events.col.code admin.rules.edit.title → admin.procedural_events.edit.title admin.rules.edit.breadcrumb → admin.procedural_events.edit.breadcrumb admin.rules.edit.field.submission_code → admin.procedural_events.edit.field.code admin.rules.edit.field.event_type → admin.procedural_events.edit.field.event_kind admin.rules.edit.field.parent → admin.procedural_events.edit.field.parent The remaining ~142 admin.rules.* keys do NOT yet have procedural_events aliases. Migrating them is a follow-up slice — each needs a new alias entry in i18n.ts (DE + EN) before the .tsx reference can be flipped. The 9 keys touched here are the most visible (page titles + edit-page field labels) so the admin UI immediately reads as "Verfahrensschritte" everywhere. * frontend/src/client/i18n.ts header comment updated to reflect that the URL rename has shipped (Slice B.6 done) and to flag the remaining i18n-key migration as the next step. Scope (documented, paliadin authorised): - "go everything" applied: backend routes + frontend nav + .tsx rebind of the 9 keys whose canonical aliases exist. - Full migration of all 142 admin.rules.* keys deferred — would require seeding ~142 new alias entries in i18n.ts (DE + EN) plus another 142 .tsx rebinds. Out of scope for tonight; flag as follow-up `feat(i18n): finish admin.rules.* → admin.procedural_events.* alias migration`. - 12 legacy /admin/rules routes still hit a handler (the redirect helper) — they don't 404 yet. Once a deprecation window passes with no traffic on the old paths, a future slice can drop them outright. Build + vet clean. TestMigrations_NoDuplicateSlot passes. This concludes the m/paliad#93 procedural-events rename slice train (Slices A through B.6). curie stays parked persistently for any follow-up the deploy / monitor cycle surfaces. |
|||
| 2c0efc396c | Merge: Slice B.5 — Go type aliases (SequencingRule = DeadlineRule) + JSON envelope dual-emit + Deprecation headers (m/paliad#93) | |||
| 5c6a0095e3 |
feat(models,services,handlers): Slice B.5 Go rename + JSON envelope dual-emit (t-paliad-305 / m/paliad#93)
Adds the Slice B.5 canonical Go names (SequencingRule, ProceduralEvent,
LegalSource, SequencingRuleService) without breaking any existing
call-site, and dual-emits / dual-accepts the two JSON envelope key
renames on /admin/api/rules with a Deprecation header.
* internal/models/models.go —
- type SequencingRule = DeadlineRule (alias; same struct, same db /
json tags). DeadlineRule remains the underlying type for now —
deferred hard-rename keeps the slice small.
- type ProceduralEvent struct mirroring paliad.procedural_events
(id, code, name, name_en, description, event_kind,
primary_party_default, legal_source_id, concept_id,
lifecycle_state, draft_of, published_at, is_active, timestamps).
Used by future code that needs the PE identity row alone.
- type LegalSource struct mirroring paliad.legal_sources (citation,
jurisdiction, pretty_de / pretty_en — both nullable per mig 136).
* internal/services/deadline_rule_service.go —
- type SequencingRuleService = DeadlineRuleService (alias).
- var NewSequencingRuleService = NewDeadlineRuleService (constructor
alias). Internal callers can adopt either name.
* internal/services/rule_editor_service.go —
- CreateRuleInput gains Code + EventKind fields tagged
json:"code" / json:"event_kind". CoalesceCanonicalKeys() folds
canonical → legacy after json.Decode so the rest of the service
keeps using SubmissionCode / EventType. Canonical wins when
both are sent.
- RulePatch gains EventKind field with the same fold.
* internal/handlers/admin_rules.go —
- adminRuleResponse wraps *models.DeadlineRule and adds Code +
EventKind fields alongside the legacy SubmissionCode /
EventType. Outputs both keys per response for one
deprecation-window slice.
- wrapRuleResponse / wrapRuleListResponse helpers.
- adminRuleDeprecationHeaders emits IETF Deprecation + Link/Sunset
headers on every Rule-bearing response so clients see the
migration signal in transit.
- All 8 Rule-returning handlers (List, Get, Create, Patch, Clone,
Publish, Archive, Restore) now wrap their result and add the
headers.
- Create + Patch handlers call CoalesceCanonicalKeys after decode
so legacy AND canonical request bodies are both accepted.
Scope decisions (documented in commit):
- Type renames use aliases instead of a hard 200-LOC rename. Same
semantics, no call-site churn. A future cleanup slice can flip
the underlying type definitions when convenient.
- ProceduralEvent + LegalSource are NEW structs (not aliases) since
they represent new conceptual rows; no legacy callers exist yet.
- Frontend admin .tsx i18n key rebinds (mentioned in parent task
brief B.5 deliverable list) are deferred — i18n keys themselves
already exist from Slice A (t-paliad-262); rebinding only changes
which key the .tsx file looks up. Pulling this into B.5 ballooned
scope; flagging as a small follow-up slice or B.6 sibling.
- Only /admin/api/rules emits dual keys today. Other handlers that
surface rule rows (Schriftsätze list, deadlines join) continue to
emit the legacy keys via models.DeadlineRule's existing JSON tags
— they're read paths, not the editor surface, and the deprecation
signal is most important where clients write.
Build + vet clean. TestMigrations_NoDuplicateSlot passes.
|
|||
| 6e0961cc30 | Merge: Composer Slice C — building blocks library + section picker (mig 149) (m/paliad#141) | |||
| ee98db94fa |
feat(submissions): Composer Slice C — building blocks library (m/paliad#141)
Per the design at docs/design-submission-generator-v2-2026-05-26.md §8
and the Q2 / Q9 ratifications:
- Q2 (m, 2026-05-26): building blocks are plain text paste sources.
No building_block_id reference is stored on submission_sections.
- Q9 (m, 2026-05-26): four visibility tiers — private / team / firm
/ global.
Schema (mig 149):
- paliad.submission_building_blocks — library catalog. Columns: slug,
firm (NULL = cross-firm), section_key (binds to one section kind),
proceeding_family (NULL = any), title_de/_en + description_de/_en
+ content_md_de/_en, author_id, visibility (CHECK in 4-tier set),
is_published, created_at, updated_at, deleted_at (soft delete).
RLS: coarse-grained SELECT — every authenticated user sees
non-deleted non-private rows + own private rows. Tier-specific
predicate (private/team/firm/global) applied in Go-layer service so
semantics evolve without RLS migrations. Mutations admin-only (no
RLS write paths).
- paliad.submission_building_block_admin_versions — append-only
history per block, retention=20. Admin-side only; NOT referenced
from submission_sections (per Q2's plain-text-paste model). Exists
so accidental delete / overwrite are recoverable.
Backend:
- internal/services/submission_building_block_service.go (~510 LoC):
BuildingBlockService. ListVisible applies tier predicate at query
time (private = author_id match; firm = firm column NULL OR matches
branding.Name; team = author shares a project_team with caller via
paliad.project_teams self-join; global = open). ListAllForAdmin
drops the predicate. Create + Update + SoftDelete + RestoreVersion
all transactional; appendVersionTx writes one audit row +
GC-deletes anything past the retention=20 horizon in the same tx.
InsertIntoSection (the paste mechanic) clones content_md_<lang>
into the section row with a "\n\n" separator if section already has
content. NO building_block_id stamped per Q2.
- internal/handlers/submission_building_blocks.go (~480 LoC): nine
handlers split between the lawyer-facing picker (list, insert) and
the admin editor (list, get, create, update, delete, list-versions,
restore-version, page). buildingBlockUpdateInput uses presence-
tracking UnmarshalJSON for the four nullable fields (firm,
proceeding_family, description_de/_en) so PATCH can distinguish
"no change" from "set to null".
- Routes registered: lawyer-facing under /api/submission-building-blocks,
admin-gated under /api/admin/submission-building-blocks/* and
/admin/submission-building-blocks (page).
- Wiring: handlers.Services + dbServices + cmd/server/main.go all
gain SubmissionBuildingBlock. NewBuildingBlockService takes the
branding.Name firm hint for the visibility predicate.
Frontend:
- frontend/src/admin-submission-building-blocks.tsx (~85 LoC):
three-pane admin shell (list / editor / version log) registered
in build.ts.
- frontend/src/client/admin-submission-building-blocks.ts (~370
LoC): admin client — list paint, edit form (slug + firm +
section_key + proceeding_family + title/desc/content per lang +
visibility radio + is_published toggle), per-block version log
with restore button. Bilingual labels.
- frontend/src/client/submission-draft.ts: per-section "+ Baustein"
button on the Composer editor toolbar (Slice B substrate gets one
more affordance). openBlockPicker opens a modal filtered to the
section's section_key, 200ms-debounced search by free text against
title/description/content. Click a hit → POST insert-into-section
→ section row's content_md_<lang> gains the block's content
appended at the end (Q2's plain-text paste semantic, no lineage).
- ~240 LoC of CSS: modal overlay + picker rows with tier-colored
visibility chips + admin editor 3-pane grid + form rows + version
list.
- 12 new i18n keys × 2 langs (admin.building_blocks.*).
Tests:
- TestValidVisibility (8 cases including case-sensitivity + empty).
- TestAppendBlockContent (8 cases covering empty-existing / empty-
addition / whitespace-only / trailing newline collapse).
- TestBuildingBlockVisibilityConstants pins the 4 string literals
against drift (RLS predicate + DB CHECK depend on them).
Build hygiene: go build/vet/test -short clean; bun run build clean
(2906 i18n keys, data-i18n scan clean).
Hard rules per ratifications honoured:
- Q2: no building_block_id lineage on sections (paste is plain text).
- Q9: 4 visibility tiers (private/team/firm/global).
- NO behavior change for pre-Composer drafts (the picker just doesn't
show — section list is hidden for base_id NULL drafts).
- {{rule.X}} aliases preserved (block content goes through the same
v1 placeholder pass on export as section prose).
NOT in scope per Slice C brief:
- User-authored private blocks (Slice C ships admin curation only;
any-user create is a follow-up).
- Tier promotion review workflow (admin sets tier directly today).
- Per-section "where is this block used" reverse lookup (no lineage
to query).
- Slice D's rich-prose features (headings, lists, blockquote) still
Slice D's job; this Slice doesn't extend the MD walker.
t-paliad-315 Slice C
|
|||
| 987db27831 | Merge: t-paliad-305 — Slice B.4 destructive drop: paliad.deadline_rules retired, INSTEAD OF triggers on view (mig 140, snapshot pre_140 same-TX) (m/paliad#93) | |||
| 1129baba7a |
feat(db,services): Slice B.4 destructive drop — paliad.deadline_rules retired, INSTEAD OF triggers on view route writes (mig 140, t-paliad-305 / m/paliad#93)
Drops the legacy paliad.deadline_rules table after 3 weeks of dual-write
shadowing (mig 136 → B.2 dual-write → B.3 read cutover via view). The
new tables — paliad.procedural_events, paliad.sequencing_rules,
paliad.legal_sources — are the sole source of truth from this commit
forward.
Pre-flip drift verified clean against prod:
deadline_rules=231 == sequencing_rules=231 == procedural_events=231
legal_sources=87
missing_sr=0, orphaned_sr=0, mismatched_lifecycle=0
* internal/db/migrations/140_drop_deadline_rules.up.sql (new) —
Single TX, audit-first:
1. CREATE TABLE paliad.deadline_rules_pre_140 AS TABLE paliad.deadline_rules
(precedent migs 091/093/095/098 — snapshot in same TX as destructive op).
2. Final reconciliation UPDATE on paliad.deadlines (no-op when
drift is already 0; defensive against last-minute writes).
3. DROP TRIGGER deadline_rules_audit_aiud.
4. Re-point FKs to sequencing_rules:
- paliad.appointments.deadline_rule_id → paliad.sequencing_rules(id)
- paliad.deadline_rule_backfill_orphans.resolved_rule_id → paliad.sequencing_rules(id)
(the id values are identical — sr.id inherited dr.id at mig 136.)
5. DROP COLUMN paliad.deadlines.rule_id.
6. DROP TABLE paliad.deadline_rules.
7. CREATE INSTEAD OF INSERT + INSTEAD OF UPDATE triggers on
paliad.deadline_rules_unified. Triggers route writes into the
three new tables in the same TX, preserving the legacy column
shape on the wire so RuleEditorService SQL only needs a
table-name swap, not a structural rewrite. Synthetic-code mint
expression is byte-identical to mig 136 + the B.2 dual-write
helper. POST assertions confirm the table is gone, the column
is gone, and the snapshot matches.
Trigger design notes (1:N caveat documented in-trigger):
- PE identity columns (code, name, name_en, description, event_kind,
primary_party_default, legal_source_id, concept_id) mirror from
the writing sequencing-rule.
- PE lifecycle columns (lifecycle_state, published_at, is_active)
deliberately do NOT mirror — a draft sequencing-rule cloned from
a published source shares the source's PE; we don't want the
clone's 'draft' lifecycle to leak back onto the source's PE.
Practical bound today (1:1 corpus); explicit comment in-trigger
for the eventual 1:N pattern.
* internal/db/migrations/140_drop_deadline_rules.down.sql (new) —
Best-effort restore from the snapshot. Triggers / indexes /
CHECK constraints from historical migrations are NOT replayed;
operator must reapply 078/079/091/095/098/122/128/134/135 to
bring the restored table to working shape. The down path is for
catastrophic recovery, not casual revert.
* internal/services/rule_editor_service.go —
Six syncDualWriteFromDeadlineRule(...) calls removed (the
INSTEAD OF triggers now do the fan-out). Five
INSERT/UPDATE paliad.deadline_rules statements (Create,
UpdateDraft, CloneAsDraft INSERT+SELECT, Publish, peer-archive,
flipLifecycle) renamed to paliad.deadline_rules_unified —
trigger handles the routing.
* internal/services/rule_editor_orphans.go — ResolveOrphan no
longer writes deadlines.rule_id (column dropped). Sets
sequencing_rule_id directly + derives procedural_event_id from
the matching sequencing_rules row in the same UPDATE statement.
* internal/services/deadline_service.go — deadlineColumns now
lists sequencing_rule_id (Deadline.RuleID still binds to it via
the db tag rename below). Update path's appendSet("rule_id",…)
flipped to appendSet("sequencing_rule_id",…) and post-write
derivation moved to the renamed syncDeadlineProceduralEventID
helper.
* internal/services/projection_service.go,
internal/services/submission_vars.go — `WHERE rule_id = $X`
reads on paliad.deadlines flipped to sequencing_rule_id.
* internal/models/models.go — Deadline.RuleID db tag changed from
"rule_id" to "sequencing_rule_id". Field name + JSON name kept
for backward compat with the frontend and existing Go callers;
semantic value is identical (same UUID).
* internal/services/dual_write.go — Massively trimmed.
Removed: syncDualWriteFromDeadlineRule, syncDeadlineDualLinks,
CheckDualWriteDrift, DualWriteDriftReport, HasDrift,
StartDualWriteDriftCheckLoop. All referenced
paliad.deadline_rules which no longer exists.
Kept (renamed): syncDeadlineProceduralEventID — derives
procedural_event_id from sequencing_rule_id after any
DeadlineService.Update that touched the back-link.
* cmd/server/main.go — Removed the StartDualWriteDriftCheckLoop
bootstrap call (and its `time` import that only that call
needed). Comment notes the retirement.
* internal/services/dual_write_test.go — Removed the final
CheckDualWriteDrift assertion in
TestDualWrite_RuleEditorLifecycle (function deleted). The
per-step asserts against procedural_events / sequencing_rules
/ legal_sources cover the same contract by direct query.
Hard rules followed:
- Audit-first: snapshot precedes destructive ops in the same TX.
- No silent data loss: pre-drop drift was zero; snapshot captures
the final state; FK re-points use identical UUIDs.
- INSTEAD OF triggers documented in mig 140 — single source of
truth for the legacy→new mapping.
- Down migration is honest about its scope (catastrophic recovery
only).
Build + vet clean. TestMigrations_NoDuplicateSlot passes. Live-DB
tests skipped (no TEST_DATABASE_URL in this env) — they'll exercise
the full mig 140 + INSTEAD OF triggers in CI.
|
|||
| c20e935a4b | Merge: t-paliad-313 — Composer Slice B: editable prose + anchor-spliced render + MD→OOXML walker (m/paliad#141) | |||
| f963b0df34 |
feat(submissions): Composer Slice B — editable prose sections + anchor-spliced render (m/paliad#141)
The "Composer actually works" milestone per the design at
docs/design-submission-generator-v2-2026-05-26.md §12 Slice B. Builds on
Slice A's substrate (submission_bases, submission_sections, base_id on
drafts); no new migrations needed.
Backend additions:
- internal/services/submission_md.go (~240 LoC): Markdown → OOXML
walker. Per the head's Slice B brief, scope is paragraphs +
bold/italic + blank-line spacing. Placeholders pass through
unchanged for the v1 substitution pass. CRLF normalisation; nested
formatting (***bold-italic***); two delimiter forms (* and _);
XML-escaping for &/</>; explicit empty-paragraph emit so blank
lines round-trip. 12 unit tests.
- internal/services/submission_compose.go (~470 LoC): SubmissionComposer
service. Pipeline: ConvertDotmToDocx pre-pass → extract
word/document.xml → render each included section's content_md_<lang>
→ splice via {{#section:KEY}}/{{/section:KEY}} anchor pairs in
the body → strip anchors for excluded sections → append unanchored
sections before <w:sectPr> → repack zip → run v1 placeholder pass.
RE2-friendly anchor scanner walks markers in body-order and matches
open/close pairs with a stack (handles unbalanced anchors
defensively). 6 unit tests covering anchor-mode splice,
append-mode-no-anchors, excluded-section drop, placeholder
resolution, lang column pick, order_index ASC.
- internal/services/submission_section_service.go: SectionPatch +
Update method. Six optional fields (content_md_de/en, included,
label_de/en, order_index). Sentinel ErrSubmissionSectionNotFound on
RLS-filtered miss.
- internal/handlers/submission_sections.go (NEW, ~150 LoC):
PATCH /api/submission-drafts/{draft_id}/sections/{section_id}.
Owner-scoped via SubmissionDraftService.Get; section-belongs-to-draft
cross-check. 404 on both missing-draft and section-belongs-elsewhere
paths.
- internal/handlers/files.go: fetchComposerBaseBytes + composerBaseSlugMap
reuse the existing Gitea proxy cache for base .docx bytes. hlc-letterhead
→ existing firmSkeletonSubmissionSlug, neutral → existing
skeletonSubmissionSlug.
- internal/handlers/submission_drafts.go: exportSubmissionDraft helper
branches on draft.BaseID. When set AND base + bytes + sections all
resolve → Composer pipeline. Else v1 fallback render path stays.
Audit metadata jsonb gains "composer": true + "base_id" flag when
composer was used.
Wiring:
- handlers.Services gains SubmissionComposer.
- dbServices.submissionComposer wired from svc.SubmissionComposer.
- main.go instantiates NewSubmissionComposer with the existing
SubmissionRenderer (so the {{rule.X}} alias contract stays preserved
inside section content).
Frontend additions (~400 LoC):
- client/submission-draft.ts: paintSectionList rewritten to render a
contentEditable per included section with a per-section B/I
toolbar. Per-section autosave debounced 500ms; mousedown handlers on
toolbar buttons preserve editor focus mid-command. domToMarkdown
walks the contentEditable's DOM tree back to Markdown source-of-
truth (b/strong → **…**, i/em → *…*, div/p → paragraph break, br
→ newline). Updated state.view.sections in-place on PATCH success
without re-painting (avoids focus-stealing on every keystroke);
re-paints only on structural changes (included toggle, label edits,
order changes).
- client/submission-draft.ts: onSectionToggleIncluded hides/shows a
section via PATCH. flushSectionAutosave on blur force-flushes
pending edits so leaving an editor doesn't strand unsynced changes.
- styles/global.css: editor surface (contentEditable area with focus
ring + placeholder), toolbar buttons (B/I 1.8rem squares),
per-section "Hide"/"Include" toggle in the head row.
- Updated i18n hint copy: "Inhalt pro Abschnitt — Autosave nach
500ms. Letztes Layout in Word."
Templates regenerated on Gitea:
- _skeleton.docx → composer-mode body (anchors only): blob SHA
ac0cdeaf49f7cd417ec143e2319ffbb02ec65644.
- _firm-skeleton.docx → composer-mode body (anchors only, preserves
sectPr → firm header/footer rIds): blob SHA
f1e9a9fb9a29ca01bf7bee709a45c5dda2a8e317.
- Both uploaded as mAi via --netrc-file ~/.netrc-mai.
- gen-skeleton-submission-template script gains an -anchors flag
(default true) so future regens emit composer-ready bodies. The
_firm-skeleton.docx regen was done via a one-off /tmp helper since
the gen-hl-skeleton-template script requires the proprietary .dotm
source which lives in HL/mWorkRepo; extending that script to accept
an existing .docx as input is a follow-up cleanup.
Build hygiene: go build/vet/test -short ./internal/... ./cmd/... all
clean; bun run build clean (2900 i18n keys, data-i18n scan clean).
NO behavior change for pre-Composer drafts (base_id NULL → v1
fallback render path stays compiled in). NO migrations needed in this
slice — sections were already in the schema from Slice A; only
content_md_de/en UPDATEs happen via the new PATCH endpoint.
Hard rules per Q2/Q10 ratification still honoured:
- No building_block_id lineage (Slice C territory; Q2).
- Caption/letterhead/signature are regular prose sections, seeded from
base spec; lawyer can edit/hide freely (Q10).
- {{rule.X}} aliases preserved (renderer pass unchanged).
NOT in scope per Slice B brief:
- Headings 1–3, lists, blockquote (Slice D's MD walker extension).
- Building blocks library (Slice C).
- Reorder / add-custom-section (Slice F).
- Auto-upgrade of pre-Composer drafts (Slice C — explicitly NOT in
this slice per head's brief msg #2393).
t-paliad-313 Slice B
|
|||
| 6cd340300b | Merge: t-paliad-313 — Composer Slice A: base picker + read-only section list (migs 146/147/148) (m/paliad#141) | |||
| 557f9a4cce | Merge: fix(paliadin): one-shot fallback when persona lacks streaming (unblock chat) | |||
| 3af71e772b |
fix(paliadin): fall back to one-shot when aichat persona lacks streaming
Symptom: paliadin chat returns "Verbindung verloren" because aichat's
paliadin persona is not configured with streaming support — every
RunTurnStream() call gets back HTTP 400 unsupported_streaming and the
SSE stream closes empty.
Fix: when RunTurnStream() detects "unsupported_streaming" in the
upstream error, transparently retry against /chat/turn (non-streaming)
with the same body. The full response gets emitted as a single
StreamChunk + StreamMeta so the SSE relay sees identical event
ordering. Persistence (completeTurn + markPrimed) mirrors the one-shot
RunTurn() path.
No real-time chunking until the persona is reconfigured upstream, but
the chat works end-to-end. Once the paliadin persona supports streaming
on aichat, this code path goes dormant — the unsupported_streaming
branch is only entered when the upstream actually returns that error.
Diagnostic logs from commit
|
|||
| e2969fc358 |
feat(submissions): Composer Slice A — base picker + read-only section list (m/paliad#141)
The first slice of the Submission generator v2 ("Composer") per the
design at docs/design-submission-generator-v2-2026-05-26.md §12 Slice A.
Ships the base concept + per-draft section seeding end-to-end with NO
change to the .docx render path — v1 export still works exactly as
today.
Schema (mig 146/147/148):
- paliad.submission_bases — catalog table; one row per template base
(slug, firm, proceeding_family, label_de/en, gitea_path, section_spec
jsonb, is_default_for[]). RLS: wide-open SELECT for authenticated
users, mutations admin-only (handler-enforced, no RLS write paths).
Seeded with 2 rows: hlc-letterhead → _firm-skeleton.docx; neutral →
_skeleton.docx. Each section_spec carries the 10-section default
(letterhead, caption, introduction, requests, facts, legal_argument,
evidence, exhibits, closing, signature) with bilingual labels +
bag-driven seed Markdown for caption/letterhead/signature.
- paliad.submission_drafts gains base_id (FK SET NULL, optional) +
composer_meta jsonb (default '{}'). Purely additive; pre-Composer
drafts keep base_id NULL → v1 fallback render path stays active.
- paliad.submission_sections — per-draft section rows (draft_id,
section_key, order_index, kind ∈ {prose,requests,evidence},
label_de/en, included, content_md_de/en). RLS mirrors
submission_drafts (owner-scoped + can_see_project, four policies).
Backend:
- BaseService (read-only Slice A): List + GetByID + GetBySlug +
GetDefaultForCode (firm/family fallback chain).
- SectionService: ListForDraft + Get + SeedFromSpec (transactional
multi-INSERT).
- SubmissionDraftService.AttachComposer wires both; Create resolves
the firm default base and seeds base_id + section rows in one tx.
Composer wiring is additive — when bases==nil the service stays
v1-shaped.
- Update accepts BaseID **uuid.UUID (set / clear / no-change).
- submissionDraftView gains BaseID, ComposerMeta, Sections fields.
- Routes: GET /api/submission-bases (catalog list). PATCH endpoints
on both project-scoped and global drafts accept "base_id".
Frontend:
- submission-draft.tsx: base picker dropdown above language toggle
(hidden until catalog loads); section-list pane above the preview
(hidden when no rows).
- client/submission-draft.ts: loadBases() parallel-fetches on boot;
paintBasePicker rebuilds <option> list on every paint; onBaseChange
PATCHes base_id and repaints; paintSectionList renders each section
read-only (label + kind chip + excluded badge + Markdown body).
- Per the brief: NO auto-upgrade of existing 11 drafts (that's Slice C).
Pre-Composer drafts get the picker (catalog still loads) but the
section pane stays hidden until they pick a base on a new draft.
Tests:
- TestFamilyOfCode + TestBaseSectionSpec_DecodeShape + _EmptyDecode
(pure unit, no DB).
- TestComposerSeedFlow (live, TEST_DATABASE_URL-gated): asserts mig 146
seeded 10 default sections on both bases; GetDefaultForCode picks
hlc-letterhead for HLC/de.inf.lg.erwidg; new draft via Create seeds
base_id + 10 section rows in tx with ascending order_index and
bilingual labels populated.
NO behavior change to .docx export — the v1 path stays sole render
path this slice. Composer's anchor-based assembly engine + MD→OOXML
walker land in Slice B.
Build hygiene: go build/vet/test -short clean; bun run build clean
(2900 i18n keys, data-i18n scan clean).
t-paliad-313
|
|||
| 85d0cedd22 | Merge: t-paliad-312 — PRD for submission generator v2 (Composer); 12 questions ratified (m/paliad#141) |