Commit Graph

1358 Commits

Author SHA1 Message Date
mAi
5468a7259d Merge: t-paliad-356 Slice 4 — name-composition token-template editor on /settings
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 12:47:21 +02:00
mAi
230306518d Merge remote-tracking branch 'origin/main' into mai/fermi/coder-implement-nomen 2026-06-01 12:46:58 +02:00
mAi
6e56b9d51f feat(settings): name-composition token-template editor (t-paliad-356 Slice 4)
Adds the /settings "Namensschemata" tab so users can customise the two wired
name artifacts (submission_draft_title, submission_docx_filename) via a
single-line {token} template, with a clickable palette, live preview, and
reset-to-default — PRD §7.

Engine (pure, pkg/nomen):
- Composition.Template() serialises a composition to "{var}" shorthand;
  ParseTemplate() is its inverse — tokens + literal separators (trailing,
  owned by the left segment) + paren Wrap. Missing-rules are NOT in the
  shorthand (PRD §7); the parser leaves every segment KindOmit. Leading /
  trailing literals are rejected (the trailing-separator model can't carry
  them) so a save never silently drops characters. Table + round-trip tests.

Paliad glue (internal/services/name_template.go):
- ParseNameTemplate overlays each segment's missing-rule from the artifact's
  system default and validates against the catalog.
- PreviewNameComposition renders against the fixed PRD sample (Bayer AG / UPC
  / Sandoz / UPC_CFI_123/2026 / today) and an empties resolver so the
  missing-rule behaviour is visible. The frontend never parses templates —
  the nomen engine stays the single source of truth.
- SettingsNameArtifacts / SettingsNameArtifact build the per-artifact cards
  (current template, system default, override flag, ordered palette, previews).

API (internal/handlers/name_compositions.go):
- GET    /api/me/name-compositions               — cards
- POST   /api/me/name-compositions/preview        — live preview + validation
- PUT    /api/me/name-compositions/{artifact_id}   — store override
- DELETE /api/me/name-compositions/{artifact_id}   — reset to system default
Storage reuses the Slice-3 service surface (UserNameCompositions /
SetUserNameCompositions) via read-modify-write; no new column, no migration.

Frontend: new tab + JS-built cards (palette insert-at-cursor, 250ms-debounced
preview, save/reset, DE/EN labels), CSS, and i18n keys (de + en).

Gates: go vet, go test ./..., bun build all clean. Browser verification of the
settings UX is deferred to post-deploy mai-tester — the shared Supabase login
wall blocks pre-merge browser login (same ceiling as t-paliad-354).
2026-06-01 12:46:07 +02:00
mAi
375d631f1b Merge: t-paliad-358 A-S1 — fill firm.signature_block + fix generate-fallback junk (merge-safe Rubrum fallback)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 12:40:53 +02:00
mAi
e3a604b4c4 Merge remote-tracking branch 'origin/main' into mai/bohr/coder-rubrum-letterhead 2026-06-01 12:40:40 +02:00
mAi
0763b7daa2 feat(submissions): fill firm.signature_block + fix generate-fallback junk (t-paliad-358 A-S1)
Two letterhead/Rubrum auto-fill fixes (Option A, no schema change):

1. firm.signature_block: was hardcoded "" ("reserved for Phase 2"), so every
   template referencing {{firm.signature_block}} rendered blank. Now filled
   from branding.Name — the firm identity line of a submission's signature
   block (the signature section seeds with signature_block + user.display_name).
   Firm-agnostic: a FIRM_NAME redeploy signs with the right firm.

2. Generate-fallback junk (kepler audit §1 Path 3): resolveSubmissionTemplate is
   the merge-path resolver (every caller feeds merge.go), but its lower tiers
   fetched _firm-skeleton.docx / _skeleton.docx — which were repurposed into
   anchors-only Composer bases (t-paliad-313 Slice B). Their bodies hold only
   {{#section:KEY}} markers, which placeholderRegex ignores, so merge.go emitted
   them verbatim as literal "{{#section:letterhead}}…" junk for every code
   without a per-code template (i.e. everything except de.inf.lg.erwidg).

   Fix:
   - docx.BuildFallbackSkeleton(lang): in-process, lang-aware, merge-safe basic
     Schriftsatz with a data-driven basic Rubrum (real {{key}} placeholders the
     var bag fills). Always available, no Gitea round-trip.
   - docx.HasMergePlaceholders guards tiers 3/4/5: a fetched skeleton is used
     only if it carries real placeholders, else we fall through to the embedded
     fallback. Today's anchors-only/placeholder-free files are skipped; a future
     merge-safe firm-skeleton (with letterhead) is preferred again automatically.
   - merge.go strips stray {{#section:…}}/{{/section:…}} markers defensively so
     no anchors-only carrier can ever leak Composer junk into a merged document.

Verified: confirmed live that deployed _firm-skeleton.docx + _skeleton.docx are
anchors-only (fetch+unzip); unit tests cover BuildFallbackSkeleton rendering a
real Rubrum (de+en), HasMergePlaceholders classification, marker stripping, and
the signature_block fill. go build / vet ./... / test ./... + bun build clean.

Out of scope (flagged for next slices): demo template's closing prints
{{firm.name}} then {{firm.signature_block}} (=firm.name) → A-S2 dedups the demo
wording. Restoring firm letterhead chrome to the merge fallback → A-S3.
2026-06-01 12:39:53 +02:00
mAi
c4e3a74e35 Merge: t-paliad-359 regroup draft-editor sidebar — naming (name+keyword) then template (base+language)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 12:38:08 +02:00
mAi
9d234f275f fix(submissions): regroup draft-editor sidebar — naming (name+keyword) then template (base+language) 2026-06-01 12:37:26 +02:00
mAi
83d5ed27e0 Merge: t-paliad-356 Slice 3 — per-user name-composition overrides (system→user precedence, mig 160)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 12:21:27 +02:00
mAi
73f379d305 feat(submissions): per-user name-composition overrides, system→user precedence (t-paliad-356 Slice 3)
PRD §3. A user can now override the system-default name composition for an
artifact; rendering prefers a valid user override over the system default.
No UI yet (Slice 4) — overrides are settable via the service API + tests.

- Migration 160: users.name_compositions jsonb NOT NULL DEFAULT '{}' — a
  { artifact_id: Composition } map.
- NameCompositionSpec (internal/services/name_composition_spec.go): Validate
  (write: known artifact + segments reference the artifact's catalog vars)
  and SanitizeForRead (read: drop unknown artifacts, drop segments with
  unknown vars, clamp version) — mirrors DashboardLayoutSpec. Plus
  nomen.Composition.SanitizeForRead and lowercase JSON tags on the nomen
  types so the stored shape is stable.
- Resolution: resolveComposition(artifact, overrides) returns the user
  override when present + valid, else the system default. The firm slot
  (PRD §3.1) is reserved for Slice 5 — system is the fallback below user
  here. Wired into BOTH artifacts: the draft-title via the create path
  (newDraftName → autoNameFor{Project,NonProject}) and the .docx filename
  via RenderSubmissionFilenameFor in the three download handlers.
  AutoSubmissionTitle / RenderSubmissionFilename stay as the nil-override
  (system-default) references the #155/354 matrices pin.
- Per-document keyword override generalised: writes now land at
  composer_meta.name_overrides.keyword (the general {var:value} shape);
  reads honour both that and the legacy composer_meta.filename_keyword
  (back-compat). The read moved to services.SubmissionFilenameKeyword
  (handlers delegate) so it is live-testable.

BACK-COMPAT FINDING: the one shipped filename_keyword row needs no data
migration — the read resolves legacy composer_meta.filename_keyword as
name_overrides.keyword. Verified by a live round-trip (seed legacy jsonb →
Get → SubmissionFilenameKeyword == the legacy value).

Verified (TEST_DATABASE_URL): (a) name_compositions Set/Get round-trip +
write-time Validate rejection of an out-of-catalog variable; (b) a user
override beats the system default for the title (through Create:
'Klageerwiderung <date>' vs default '<date> Klageerwiderung') and the
filename (through RenderSubmissionFilenameFor), with the nil-override
default unchanged; (c) legacy filename_keyword back-compat read. Plus unit
tests for nomen.SanitizeForRead, NameCompositionSpec Validate/Sanitize/
resolve, and the keyword back-compat read. go vet + go test ./... (15
pkgs) + bun build clean. No Playwright (no UI this slice).
2026-06-01 12:20:19 +02:00
mAi
9a5ee93f2e Merge: t-paliad-357 Rubrum+letterhead auto-fill audit (gap map)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 12:13:51 +02:00
mAi
213be10ada docs(submissions): gap map — Rubrum + letterhead auto-fill from project data (t-paliad-357)
Read-only audit. Maps the 3 doc-generation fill paths (merge.go fills
header/footer placeholders; compose.go passes headers byte-for-byte;
skeleton-as-merge-fallback latent bug), three gap tables (template /
var-bag / data-model), forum-dependence grounded on UPC RoP r.13, and a
tracer-bullet-first wiring proposal. States the A/B fork for m: wire what
current data supports (basic caption, no schema) vs. forum-correct Rubrum
(structured party address/Rechtsform/Sitz + court details + capture UI).
2026-06-01 12:12:10 +02:00
mAi
6dd9befba3 Merge: t-paliad-356 Slice 2 — non-project drafts get date-first name via nomen engine
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 12:06:43 +02:00
mAi
e10b5e6546 feat(submissions): non-project drafts get a date-first name (t-paliad-356 Slice 2)
PRD §6. Project-less drafts no longer fall back to the bare 'Entwurf N'
counter — they render the submission_draft_title artifact through the
nomen engine, leading with the date like project drafts do (m's ask).

newDraftName's non-project branch now resolves a keyword (document type)
from the draft's submission_code and renders '<date> <keyword>', e.g.
'2026-06-01 Klageerwiderung'. When the code has no published filing rule
the keyword degrades to the localized 'Entwurf'/'Draft' word, yielding
'<date> Entwurf'. Collisions get the usual ' (N)' suffix via
uniqueDraftName.

FLAG resolved: project-less drafts DO carry a required submission_code
(the global-create handler rejects an empty code), and submission_code ->
rule name is a function across the published filing rules (the code
encodes the proceeding, e.g. de.inf.lg.erwidg -> Klageerwiderung), so the
keyword resolves project-independently via a LIMIT 1 catalog lookup. Both
branches (rule-name / Entwurf fallback) flow through the same composition.

The keyword is a new, normally-omitted segment on the title composition;
project drafts leave it empty so they render identically to #155 — the
regression guard. The identity trio and the keyword are mutually exclusive
by construction (project => trio, no project => keyword).

Verification: PRIMARY gate is a service-level live test (TEST_DATABASE_URL)
asserting Create(projectID=nil) yields '<date> <keyword>', the ' (2)'
collision suffix, EN document type, and the '<date> Entwurf'/'Draft'
fallback — all green against the real DB. Existing project-title matrix
(TestAutoSubmissionTitle) unchanged. go vet + go test ./... + bun build
clean. Browser (Playwright) verification not meaningful pre-merge: S2 is
not yet deployed (only S1/cd3f784 is) and the naming is server-side in
Create behind the shared-auth login wall.
2026-06-01 12:05:57 +02:00
mAi
cd3f7843a7 Merge: t-paliad-356 Slice 1 — nomen name-composition engine + fold in #155/354 schemes (byte-equal refactor) + PRD
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 11:57:13 +02:00
mAi
4920328b09 feat(nomen): name-composition engine + fold in the two shipped schemes (t-paliad-356 Slice 1)
Slice 1 of the filename-generator train (PRD 2026-06-01 §8). Pure refactor
behind byte-equality — no user-visible change.

pkg/nomen: the reusable engine. A Composition (ordered Segments, each with
a trailing separator, optional wrap, and an omit/placeholder/literal
missing-rule) renders against a VarResolver and a RenderTarget. Targets
split into SanitiseValue (per-variable) + Finalise (whole-string + suffix)
so a human title and a sanitised filename are two targets of one
composition. VarCatalog + Validate guard stored compositions.

internal/services/namegen.go: paliad-side wiring — the two seed system-
default compositions that reproduce AutoSubmissionTitle (#155) and
submissionFileName (354) as DATA, their variable catalogs, the resolvers
(built from the existing submission_autoname helpers), and the artifact
registry binding artifact -> catalog -> target -> default.

Repointed call-sites: AutoSubmissionTitle and handlers.submissionFileName
are now thin wrappers rendering through the registry; the assembly logic
lives in the engine. Removed the hardcoded title/filename assembly and the
handler's Az.-folgt const (now the case_number segment's placeholder).

FLAG resolved (separators): the PRD sketched LEADING separators; that can't
reproduce #155's client-absent case (date must join forum with a space
while forum->opponent stays ' ./. '). Switched to TRAILING separators
(owned by the left segment) — the minimal faithful fix. PRD §2.1 annotated.

FLAG resolved (back-compat): the shipped composer_meta.filename_keyword
override still flows through the engine — live round-trip test green.

Acceptance: all existing #155/354 test matrices pass UNCHANGED (the
byte-equality gate); new pkg/nomen unit tests cover trailing-sep, the three
missing-rules, targets, and Validate; namegen_test validates the seeds
against their catalogs. go vet + go test ./... + bun build all clean.
2026-06-01 11:56:27 +02:00
mAi
385abc7a98 docs(prd): composable name/filename generator engine (t-paliad-355)
Engine (pkg/nomen) renders a name from (composition, var-bag, render-
target). Structured segments + string shorthand; omit/placeholder/literal
missing-rules; title vs filename targets. Both shipped schemes (#155
draft title, 354 export filename) fold in as data-driven seed defaults
with byte-equality as the acceptance gate. Non-project drafts get a
date-first <date> <keyword> name (m's immediate ask).

Settings built ON the dashboard-layout-spec precedent: system -> firm ->
user -> per-document precedence, validated jsonb spec. Project-level
deferred to v1.1 (storage path reserved on projects.metadata).

5-slice train: engine+faithful-refactor -> non-project fix -> user
override -> settings UX -> firm default. All 8 grilling questions
answered (matched recommendations), captured in the decisions section.
2026-06-01 11:42:50 +02:00
mAi
94adeeb8cb Merge: t-paliad-354 generated-doc filename <date> <keyword> (<case>) + user-replaceable keyword
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 10:42:25 +02:00
mAi
d834b36313 test(submissions): live-DB round-trip for filename_keyword composer_meta merge/clear (t-paliad-354)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-06-01 10:40:55 +02:00
mAi
4092c889c4 feat(submissions): generated-doc filename <date> <keyword> (<case>) + user-replaceable keyword
Generated documents now download as "YYYY-MM-DD keyword (case number).docx"
(date first/sortable, case number bracketed) instead of the old
"rule-case-date.docx" shape.

- submissionFileName: date-led frame; keyword = user override > lang-aware
  rule name > "submission"; case number always bracketed, placeholder
  "Az. folgt" (named const) when the project has no Aktenzeichen.
- SanitiseSubmissionFileName hardened to fold the full Windows-reserved
  set (colon star question angle pipe) on top of slash/backslash, while
  preserving spaces + parentheses so the assembled frame stays
  human-facing yet filesystem-safe.
- User-replaceable keyword stored in the draft's composer_meta jsonb
  (filename_keyword, no migration). Editor gains a "Stichwort (Dateiname)"
  input that placeholders the auto rule name and persists via the draft
  PATCH path. One-click /generate has no draft row -> keeps auto keyword.

Tests: submissionFileName (full / no-AZ / override / EN / slash case-no /
blank override / empty rule), submissionFilenameKeyword, extended
sanitiser cases.

t-paliad-354
2026-06-01 10:35:23 +02:00
mAi
db1040968f Merge: t-paliad-352 submission draft auto-naming (m/paliad#155)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-31 15:29:32 +02:00
mAi
f292338919 feat(submissions): auto-name new drafts <date> <client>./.<forum>./.<opponent> (m/paliad#155)
New project-bound submission drafts now default to a sortable, legal-
convention title instead of the bare "Entwurf N" counter:

    <YYYY-MM-DD> <ClientName> ./. <ForumShort> ./. <OpponentName>

- Date leads (ISO, Europe/Berlin) so drafts list chronologically; " ./. "
  is the German legal "gegen" separator.
- Client = root 'client' ancestor of the project tree.
- Forum = proceeding-type jurisdiction (UPC/EPA/DPMA); German proceedings
  resolve to the deciding court (LG/OLG/BGH/BPatG) from the code tail.
- Opponent = primary opposing party, picked by our_side posture
  (active → defendant bucket, reactive → claimant bucket).
- Any segment that resolves empty is omitted with its leading separator;
  a project-less draft keeps the legacy "Entwurf N" scheme entirely.
- Create-time only: existing drafts are never renamed, and a lawyer's
  later manual rename via Update is untouched. Same-slot collisions
  de-duplicate with a " (N)" suffix.

Customization scope (per-user / firm / template, issue #155 Q4) is v1.1 —
the template is hardcoded in submission_autoname.go for now; the override
string is documented as the single extension point on AutoSubmissionTitle.

Example output:
  full:        2026-05-31 Bayer AG ./. UPC ./. Novartis Pharma
  no opponent: 2026-05-31 Bayer AG ./. BPatG
  no forum:    2026-05-31 Bayer AG ./. Novartis Pharma
  date only:   2026-05-31

AutoSubmissionTitle + segment resolvers are pure and table-tested
(submission_autoname_test.go); the Create flow is covered end-to-end
against real Postgres in submission_draft_autoname_live_test.go (gated
on TEST_DATABASE_URL).
2026-05-31 15:28:54 +02:00
mAi
2b240e7dd0 Merge: docs PRD schema corrections (planck feedback)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-31 15:16:55 +02:00
mAi
c945cbd330 docs(prd): fix 3 schema inaccuracies in litigation-planner PRD
planck flagged via mai report feedback (id 12301) after the B5+B6
verification round caught them:

- §5.4 'INSERT into paliad.project_parties' → real table is paliad.parties
- §5.4 'status=open' → real CHECK constraint allows pending/completed/cancelled/waived
- §7.4 listed verfahrensablauf-detail-mode.ts as dead code, but builder
  imports filterByDetailMode from it; struck through with KEEP note.

Code shipped (B5+B6) used the correct values throughout; this aligns
the historical PRD with reality so a future reader doesn't repeat the
verification time planck spent.
2026-05-31 15:16:55 +02:00
mAi
639ff4f672 Merge: t-paliad-350 B6 — mobile basic-read + dead U0-U4 cleanup + i18n finalise (m/paliad#153)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 20:52:06 +02:00
mAi
264cc39a6b chore(builder): B6 — mobile basic-read + dead U0-U4 cleanup + i18n finalise (t-paliad-350)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
Litigation Builder slice B6 (m/paliad#153 PRD §7.1 + §7.4 + §10) — last
slice of the train; the Builder is now complete.

Mobile basic-read (<640px, PRD §10):
- builder.ts wireMobileGuard — a capture-phase document click listener that,
  only when matchMedia("(max-width:640px)") matches, intercepts taps on
  mutating affordances (rename/share/promote/new-scenario/add-proceeding +
  every in-triplet button/input/select), preventDefault+stopPropagation,
  and surfaces a "Auf größerem Bildschirm öffnen" toast. Desktop code paths
  are untouched (guard early-returns off-mobile). Column-triplets already
  collapse to a single column via the reused .fr-columns-view @640px rule;
  reading (open/switch scenarios, search, mode tabs) stays fully functional.
- global.css — .builder-mobile-toast + full-bleed modal on phones.

Dead U0-U4 catalog cleanup (PRD §7.4) — deleted, no remaining references
(grep "from.*fristenrechner-|from.*verfahrensablauf" shows only the kept
verfahrensablauf-core + verfahrensablauf-detail-mode the builder reuses):
- client/fristenrechner-mode-a.ts, fristenrechner-result.ts(+test),
  fristenrechner-wizard.ts(+test)
- client/verfahrensablauf.ts, client/views/event-card-choices.ts,
  client/views/verfahrensablauf-state.ts(+test)
- components/VerfahrensablaufBody.tsx
(/tools/fristenrechner + /tools/verfahrensablauf stay as 301 redirects to
/tools/procedures — handlers already redirectToProcedures.)

i18n finalised (DE+EN): removed 4 duplicate deadlines.party.* keys per
block (behaviour-preserving — the later, winning copy is kept) and added
the missing DE "cal.today". Codegen clean, no dupes, full DE/EN parity.

go build/vet clean; bun build + 227 frontend tests pass. Playwright-
verified: at 375px the triplet collapses to one column + the scenario
list reads, while "+ Verfahren hinzufügen" and "Teilen" are blocked
(toast shown, no action); at 1280px the same actions work normally.
2026-05-29 20:44:40 +02:00
mAi
28d860a07d Merge: t-paliad-350 B5 — share + promote-to-project wizard (m/paliad#153)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 20:37:37 +02:00
mAi
d913f4fc30 feat(builder): B5 — share + promote-to-project wizard (t-paliad-350)
Litigation Builder slice B5 (m/paliad#153 PRD §2.4 + §2.5 + §5.4 + §10).

Backend (internal/services/scenario_builder_service.go):
- ListSharedWithMe — scenarios shared read-only with the caller (the
  "Geteilt mit mir" bucket).
- PromoteScenario — transactional promote-to-project (PRD §10, no partial
  promotions). One Postgres tx: INSERT paliad.projects ('case',
  origin_scenario_id, proceeding_type_id + scenario_flags from the primary
  triplet) → creator team lead + wizard-selected colleagues → parties →
  deadlines (filed→completed, planned→pending with computed/actual date,
  skipped→none) → flip scenario to 'promoted' + promoted_project_id. The
  primary top-level proceeding + its spawned descendants form the one case
  file; additional standalone proceedings are reported via
  ProceedingsSkipped and stay in the scenario. Planned dates come from the
  injected FristenrechnerService.Calculate; court-set/undated planned
  events are skipped + counted.
- NewScenarioBuilderService gains a *FristenrechnerService dep (wired in
  cmd/server/main.go; nil in tests that don't promote).

Handlers/routes:
- GET /api/builder/scenarios/shared, POST /api/builder/scenarios/{id}/promote.

Frontend:
- builder-shares.ts — share modal (HLC user picker + current-shares list +
  revoke).
- builder-promote.ts — 3-step wizard (Bestätigen → Parteien ergänzen →
  Akte-Metadaten) → POST /promote → navigate to /projects/{id}.
- builder.ts — bucketed side panel (Aktiv / Geteilt mit mir / Als Projekt
  angelegt / Archiviert), read-only chrome (watermark + locked affordances)
  for shared/promoted scenarios, wired share + promote buttons, deep-link
  auto-load now covers shared scenarios.
- procedures.tsx — enabled buttons, bucket containers, readonly watermark slot.
- global.css — modal scaffold, share UI, promote wizard, buckets, readonly
  state. i18n.ts + i18n-keys.ts — DE+EN keys.

Tests: TestScenarioBuilderPromote (live-DB) pins the transactional cascade
+ readonly-after-promote + re-promote rejection. go build/vet/test + bun
build clean. Verified end-to-end via Playwright: Journey E (share → 2nd
user read-only watermark + locked canvas, incl. deep-link) and Journey D
(promote wizard 3 steps → project created with party → navigate → scenario
flipped to promoted).
2026-05-29 20:37:05 +02:00
mAi
e091716f48 Merge: t-paliad-349 docforge slice 8 — neutral model + Markdown importer + Exporter iface (m/paliad#157)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 18:11:39 +02:00
mAi
8763ab013c feat(docforge): slice 8 — neutral model + Markdown importer + Exporter iface (t-paliad-349)
The final slice: land the format-neutral document model with REAL consumers
and unify the Markdown parser — no duplication, byte-identical output.

Neutral model (pkg/docforge/model.go): Document / Block / InlineSpan.
BlockKind values are the stylemap keys. A hyperlink is a span with Link set
+ Children (the label's spans), preserving link boundaries so adjacent
same-URL links stay distinct — byte-exact with the pre-model walker.

Markdown importer (pkg/docforge/markdown): Import(md) → Document. The SINGLE
Markdown parser for docforge — block split, marker detection, inline
bold/italic/link tokenisation, {{placeholder}} pass-through (the b78a984
fix). Relocated out of the docx walker.

docx renderer (pkg/docforge/docx/markdown.go): now RENDERS a Document →
OOXML (RenderDocumentToOOXML); RenderMarkdownToOOXML[WithStyles] = render(
markdown.Import(md)). The shipped submission walker routes through the model,
so there is one parser, not two. The comprehensive byte-exact render tests
(RenderMarkdownToOOXML_*) all PASS unchanged = output identical.

Exporter interface (pkg/docforge/exporter.go, PRD §4 B4): Exporter{Format,
MIMEType, RenderBody(Document)} with the .docx impl (pkg/docforge/docx/
exporter.go). The seam a future PDF/HTML exporter slots into.

Tests: parser tests relocated to the markdown pkg (parseSpans/detectBlockMarker)
+ new importer Document tests + exporter conformance test.

Verification: go build/vet clean; gofmt clean; full NO-DB test suite GREEN
(authoritative — proves no regression); docforge byte-exact render oracle
PASS; composer live test renders through the rewired walker (PASS); bun build
+ bun test 274/274. The shared-DB live run fails ~85 tests across unrelated
services from a harness pq-42P08 $1-type seeding quirk + a stale
deadline_rules test — systemic/environmental (the no-DB run is clean), not
this change.

docforge train complete: 8 slices, the engine extracted + cleaned + a working
author→generate→export loop on uploaded templates, plus the neutral model +
importer + exporter seam for future formats/consumers.

m/paliad#157
2026-05-29 18:10:16 +02:00
mAi
e1e8db7fc9 Merge: t-paliad-349 docforge slice 7 — generation on uploaded templates (m/paliad#157)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 17:57:30 +02:00
mAi
b746ec36c7 feat(docforge): slice 7 — generation on uploaded templates (t-paliad-349)
A submission draft can now render from an uploaded docforge template
instead of a legacy Gitea base. DB-VERIFIED against TEST_DATABASE_URL (the
head greenlit option C) before commit — not just compiled.

Schema: migration 159 adds submission_drafts.template_version_id (nullable,
FK template_versions ON DELETE SET NULL) — the snapshot pin (PRD A3). A
later template edit creates a new version; the pinned draft keeps rendering
its version.

Draft service: TemplateVersionID on the model + draftColumns + the JOIN
list + DraftPatch (two-level pointer like base_id) + Update SET. Column-sync
verified live (Create_seeds_section_rows + the new pin test both pass).

Export/preview (handlers): a template-version path checked FIRST — load the
carrier via TemplateStore.GetVersion, render via the existing Export/
RenderPreview (the carrier already carries {{slots}}; no Composer/sections
needed). Falls through to base_id / v1 if the pin is missing. Both preview
sites + the view assembly branch on it.

Store: TemplateMeta.VersionID exposes the current version's row id (slice-4
gap — a consumer needs it to pin); populated in List/Get/GetVersion + the
authoring JSON. New GET /api/templates (authenticated, firm-filtered) is the
picker list any lawyer reads; admin authoring endpoints stay gated.

Frontend: the submission editor's base picker now offers uploaded templates
as a 'tpl:<version_id>' optgroup; selecting one PATCHes template_version_id
(clearing base_id) and vice versa — mutually exclusive render paths.

Live test (submission_draft_template_live_test.go, gated): pin round-trips
Update→Get, the uploaded carrier renders ({{firm.name}}→HLC via Export), and
clearing nulls it — all PASS against real Postgres.

Verification: go build/vet/gofmt clean; bun build + bun test 274/274; slice-7
+ slice-4 store + draft/composer live tests PASS against TEST_DATABASE_URL.
Pre-existing env failures (approval/projection seed $1-type quirk,
migration136 stale deadline_rules table) are unrelated — confirmed my branch
touches none of that code.

m/paliad#157
2026-05-29 17:55:31 +02:00
mAi
28aaafeb05 Merge: t-paliad-349 docforge slice 6c — authoring page (frontend) (m/paliad#157)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 16:08:46 +02:00
mAi
f9331e9bb9 Merge: t-paliad-349 docforge slice 6b — authoring HTTP endpoints (m/paliad#157) 2026-05-29 16:08:46 +02:00
mAi
e53bcf8cc2 Merge: t-paliad-349 docforge slice 6a — authoring core + TemplateStore wiring (m/paliad#157) 2026-05-29 16:08:46 +02:00
mAi
68fcbc6fbf feat(docforge): slice 6c — template authoring page (frontend) (t-paliad-349)
The WYSIWYG authoring surface at /admin/templates (admin-gated page route):
  - templates-authoring.tsx — page shell (upload form, template list,
    workspace: palette / run-addressable preview / placed slots).
  - client/templates-authoring.ts — hydrates it: lists templates, uploads a
    .docx (multipart), renders the run-span preview, builds the variable
    palette from the Go catalogue (GET /api/docforge/variables), and wires
    the select-then-pick gesture: select text within one .docforge-run, click
    a palette variable → POST the slot → re-render with the response. Reuses
    the docforge-editor lib (escapeHtml, catalogue client). Cross-run
    selections rejected with a hint (v1: single-run text slots).
  - build.ts emits dist/templates-authoring.html + bundles the client.
  - handleTemplatesAuthoringPage serves the shell; GET /admin/templates
    registered under adminGate.
  - 12 i18n keys (DE+EN) for the page; i18n-keys.ts regenerated (3079).

Verification: go build/vet/test green (13 pkgs); bun run build.ts clean
(i18n scan passes); bun test 274/274; gofmt-clean. The docx surgery + store
+ catalogue are unit/live-tested. VERIFICATION CEILING: the integrated live
flow (upload→render→select→inject→save in a browser) needs the app running
with DATABASE_URL + Supabase auth + Playwright — verified post-merge, not in
this env.

m/paliad#157
2026-05-29 16:07:43 +02:00
mAi
31e15d4b20 feat(docforge): slice 6b — template authoring HTTP endpoints (t-paliad-349)
Admin-gated authoring API over docforge.TemplateStore + the docx authoring
engine (handlers/templates.go, routes under adminGate):
  GET  /api/admin/templates            — catalog list
  POST /api/admin/templates            — multipart upload → ImportForAuthoring
                                          (validate + detect slots) → Create v1
  GET  /api/admin/templates/{id}       — authoring view (run-addressable
                                          preview + slots)
  POST /api/admin/templates/{id}/slots — InjectSlot at the selection →
                                          AddVersion (re-detect slots from the
                                          new carrier so template_slots mirror
                                          the carrier's actual {{tokens}})

docforge.ErrTemplateNotFound → 404; injection failures (bad selection/key)
→ 400 with the engine's message for the UI to surface. Upload capped at
10 MB. Slot placement creates a version per placement (immutable snapshot);
batching a session into one version on explicit save is a documented
refinement.

Verification: go build/vet clean, handlers test green, gofmt-clean. The docx
surgery + store are unit/live-tested; the integrated HTTP flow is verified
post-merge (needs DATABASE_URL + Supabase auth).

m/paliad#157
2026-05-29 16:03:07 +02:00
mAi
a111a82640 feat(docforge): slice 6a — docx authoring core + TemplateStore wiring (t-paliad-349)
The verifiable backend heart of the authoring surface, before the HTTP +
frontend layers.

pkg/docforge/docx/authoring.go:
  - ImportForAuthoring(carrier) → AuthoringView{PreviewHTML, Slots}: parses
    an uploaded .docx into a run-addressable preview (one
    <span class="docforge-run" data-run="N"> per <w:t>, document order)
    plus the {{placeholder}} slots already present.
  - InjectSlot(carrier, runIndex, selectedText, slotKey) → new carrier:
    replaces the selection inside run N with a {{slot_key}} token. Keys on
    the selected TEXT (not a byte/UTF-16 offset) so umlauts can't desync the
    client selection from the server slice; preview + injection walk runs in
    the identical paragraph→<w:t> order so data-run indices line up.
  - v1 scope: text slots in body paragraphs; out-of-run / cross-run / not-
    found selections return an error the UI turns into a hint.

6 unit tests cover run-addressable preview, slot detection, injection +
round-trip re-import, umlaut/run-targeting, and the error paths (selection
absent, out-of-range run, invalid slot key) — all passing.

Wired PgTemplateStore through the stack (main.go → handlers.Services →
dbServices) so the upcoming authoring endpoints can reach it.

Verification: go build/vet clean, full module test green (13 pkgs), new
files gofmt-clean. The HTTP endpoints + frontend authoring page land next;
their live flow needs the post-merge e2e/manual loop (DB+Supabase).

m/paliad#157
2026-05-29 16:00:27 +02:00
mAi
63a9bedf7e Merge: t-paliad-349 docforge slice 5 — editor pkg + variable catalogue SSOT (m/paliad#157)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 15:51:38 +02:00
mAi
b8709b903d feat(docforge): slice 5 — docforge-editor pkg + variable catalogue SSOT (t-paliad-349)
Establish the shared frontend editor package and make the Go resolvers the
single source of truth for variable labels.

Go — catalogue SSOT:
  - VariableResolver gains Keys() []VariableKey; ResolverSet gains
    Catalogue(). The 7 submission resolvers implement Keys() with the
    bilingual labels ported from the TS VARIABLE_LABELS table (incl. the
    legacy rule.* aliases). Keys() is entity-independent, so
    SubmissionVariableCatalogue() builds a metadata-only ResolverSet.
  - GET /api/docforge/variables serves the catalogue (auth-gated, static).
  - Tests: docforge ResolverSet (BuildBag merge + Catalogue order) and the
    submission catalogue integrity (no dupes, labels present, spot-checks).

Frontend — frontend/src/lib/docforge-editor/ (new shared package):
  - dom.ts: escapeHtml + cssEscape (pure), with bun tests. Dedupes the two
    identical escapeHtml/escapeHTML copies + the cssEscape copy that lived
    in the submission editor.
  - catalogue.ts: fetchVariableCatalogue() + labelMap() — the client for
    the Go catalogue.
  - submission-draft.ts now imports escapeHtml/cssEscape from the lib and
    fetches the catalogue on boot into state.varLabels (labelFor reads it,
    falling back to the raw key if the fetch fails — graceful degrade). The
    hardcoded VARIABLE_LABELS table is removed; VARIABLE_GROUPS stays
    (presentation: which keys to show + how to section them, legitimately
    frontend).

Scope note: the DOM-coupled editor plumbing (wireDraftVars/focus
preservation/autosave debounce) is extracted in slice 6 alongside its first
reuse — the authoring page — rather than speculatively now (extract with the
consumer; same principle as slices 2-3). Slice 5 lands the pure utilities +
the catalogue, which the slice-6 authoring palette consumes.

Verification: go build/vet/test green (Go files gofmt-clean; handlers.go
pre-existing drift, added region clean); bun run build.ts clean;
bun test 274/274 (incl. 5 new docforge-editor tests).

m/paliad#157
2026-05-29 15:50:42 +02:00
mAi
938222d602 Merge: t-paliad-349 docforge slice 4 — template tables + TemplateStore (m/paliad#157)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 15:36:46 +02:00
mAi
47deeaf5ed feat(docforge): slice 4 — template tables + TemplateStore (t-paliad-349)
Persistence foundation for authoring (slice 6) + generation-on-templates
(slice 7). docforge owns no tables — it defines the contract; paliad
implements it (litigationplanner pattern).

Migration 158_docforge_templates (additive, generic — NOT submission_*-named
so a second docforge consumer reuses it):
  - templates          — catalog row; current_version_id pins the live
                         version (FK added post-create to break the
                         templates<->versions cycle; ON DELETE SET NULL).
  - template_versions  — immutable snapshots; carrier .docx in a bytea
                         column (the TemplateStore bytea backend) + stylemap
                         jsonb. Versioning = snapshot-at-create (PRD A3).
  - template_slots     — variable slots per version; anchor = sentinel token
                         locating the slot in the carrier OOXML (PRD §5
                         lean), slot_key = the bound variable.
  RLS mirrors submission_bases: firm-shared SELECT for authenticated,
  mutations admin-only + gated in Go (no mutation policy = denied).

docforge root: TemplateStore interface + neutral types (TemplateMeta,
Template, TemplateSlot, *Input, TemplateFilter) + ErrTemplateNotFound.
CarrierBytes is format-opaque []byte so the root never imports the docx
adapter; the exporter wraps (CarrierBytes, Stylemap) into a docx.Carrier.

paliad: PgTemplateStore (sqlx, follows the submission_base_service pattern):
List / Get (current version) / GetVersion (pinned snapshot) / Create
(version 1 + pin) / AddVersion (next version + re-pin), all transactional.
Gated live round-trip test (TEST_DATABASE_URL) covers carrier+stylemap+slot
round-trip and the version bump. No handler wires this yet (PRD: no UI in
slice 4).

Verification: go build ./... clean, go vet clean, gofmt clean, full module
test green, migration NoDuplicateSlot structural test green.

m/paliad#157
2026-05-29 15:35:36 +02:00
mAi
a2da501917 Merge: t-paliad-349 docforge slice 3 — VariableResolver interface + ResolverSet (m/paliad#157)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 15:27:23 +02:00
mAi
8ea78fd376 refactor(docforge): slice 3 — VariableResolver interface + ResolverSet (t-paliad-349)
Move the variable-bag contract (PlaceholderMap, MissingPlaceholderFn,
DefaultMissingMarker) up to the pkg/docforge root (placeholder.go) — it is
format-neutral, consumed by the resolver layer and any future exporter.
The {{key}} substitution grammar (placeholderRegex, PUA preview sentinels,
replacePlaceholders) stays in pkg/docforge/docx: it is the .docx renderer's
own machinery, not a root concern.

New at the root (vars.go):
  - VariableResolver{Namespace() string; Populate(bag PlaceholderMap)} —
    a PUSH interface, deliberately not pull Resolve(key): some namespaces
    emit a data-dependent key set (parties.claimant.0.name, .1.name, … one
    per party) that a fixed key-by-key pull can't enumerate.
  - ResolverSet + BuildBag() — composes resolvers into one bag, replacing
    the hard-coded addFooVars-then-addBarVars sequencing in Build.

paliad side (submission_vars_resolvers.go): seven resolver types wrap the
UNCHANGED addXxxVars push-builders (firm/today/user/procedural_event/
project/parties/deadline), each capturing the entity it needs. The builder
bodies are byte-for-byte untouched, so the bag is identical by
construction; SubmissionVarsService.Build now wires the applicable
resolvers and calls ResolverSet.BuildBag(). Resolvers stay in paliad
because they read paliad's domain model; a second docforge consumer plugs
its own resolvers into a ResolverSet the same way.

Keys()/Catalogue() (the static key list that will data-drive the authoring
palette + kill the hardcoded VARIABLE_GROUPS in submission-draft.ts) is
deferred to the UI slice that consumes it, sourced from the frontend's
existing labels — building it now, ahead of its consumer, would be
speculative (PRD §4 B3 principle).

Verification: go build ./... clean, go vet clean, full module test green.
Alias-parity (procedural_event ≡ rule) and party-form tests pass unchanged
= bag byte-identical.

m/paliad#157
2026-05-29 15:16:02 +02:00
mAi
e189d3fe6a Merge: t-paliad-349 docforge slice 2 — composer + Carrier to pkg/docforge/docx (m/paliad#157)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled
2026-05-29 15:10:33 +02:00
mAi
58907554fc Merge: t-paliad-349 docforge slice 1 — extract .docx engine to pkg/docforge/docx (m/paliad#157) 2026-05-29 15:10:33 +02:00
mAi
9b8a865c5f Merge: t-paliad-349 — docforge PRD (m/paliad#157) 2026-05-29 15:10:33 +02:00
mAi
f8067c2fe5 refactor(docforge): slice 2 — composer to pkg/docforge/docx + Carrier (t-paliad-349)
Move the full compose pipeline (anchor-pair splicing, append-before-sectPr,
hyperlink-rels patching, zip split/repack, final placeholder pass) into
pkg/docforge/docx/compose.go, decoupled from paliad's DB row types. The
engine now owns the entire .docx assembly.

New neutral types in docx:
  - Carrier{Bytes, Stylemap} — the opaque base .docx, preserved
    byte-for-byte outside the spliced regions (the lossless docforge
    carrier for .docx).
  - Section{Key, OrderIndex, Included, ContentMDDE, ContentMDEN} — the
    format-neutral content input.
  - Composer / NewComposer / ComposeOptions on those neutral types.

internal/services keeps SubmissionComposer + ComposeOptions as a thin
mapping wrapper (SubmissionSection -> docx.Section, Base.SectionSpec.Stylemap
+ BaseBytes -> docx.Carrier). handlers + the comprehensive compose_test are
unchanged; the test drives the wrapper end-to-end and its byte-exact OOXML
assertions pass = behaviour preserved.

Retired the slice-1 docx.XMLAttrEscape wrapper + its services forwarder:
compose now calls the local xmlAttrEscape inside the docx package.

Sequencing note: the paragraph-level neutral model (Document/Block/Slot the
PRD §3.2 sketches) is deferred to slice 6, where the authoring importer +
format exporters consume it. Building it now, ahead of any consumer, would
be speculative and risk the byte-identical guarantee for no gain (PRD §4 B3
principle). Carrier is the part of the model that earns its keep this cycle.

Verification: go build ./... clean, go vet clean, full module test green.

m/paliad#157
2026-05-29 14:57:34 +02:00
mAi
78a30a7ee0 refactor(docforge): slice 1 — extract .docx engine to pkg/docforge/docx (t-paliad-349)
Relocate the in-house OOXML machinery out of internal/services into the
first docforge adapter, with zero behaviour change:

  submission_merge.go  -> pkg/docforge/docx/merge.go     (placeholder
                          substitution renderer + preview-HTML emitter)
  submission_md.go     -> pkg/docforge/docx/markdown.go  (Markdown->OOXML
                          walker incl. the b78a984 underscore-fix)
  submission_render.go -> pkg/docforge/docx/dotm.go      (.dotm->.docx)
  + their _test.go files (git-tracked renames, 84-99% identical)

internal/services keeps thin type-alias + forwarder shims
(docforge_shims.go) so every caller in services/handlers/main compiles
and behaves identically: PlaceholderMap, MissingPlaceholderFn,
SubmissionRenderer, HyperlinkAllocator (aliases); NewSubmissionRenderer,
DefaultMissingMarker, RenderMarkdownToOOXML[WithStyles], ConvertDotmToDocx,
SanitiseSubmissionFileName (forwarders). docx.XMLAttrEscape is exported so
submission_compose.go's hyperlink-rels inserts reuse the walker's escaping.

Three mis-filed pretty-printer tests (legalSourcePretty, ourSideDE/EN,
patentNumberUPC) that exercise the vars layer move back to
internal/services/submission_vars_pretty_test.go.

Placeholder grammar + PlaceholderMap stay co-located with the renderer in
docx for now; slice 3 hoists the format-neutral grammar to the docforge
root with the VariableResolver interface.

Verification: go build ./... clean, go vet clean, full module test green
(the byte-exact OOXML golden tests in merge/compose/render pass unchanged
= behaviour preserved). gofmt drift on the moved files is pre-existing
(72/169 services files already drift; no gofmt gate).

m/paliad#157
2026-05-29 14:51:59 +02:00
mAi
091804923a docs(docforge): PRD — modular doc-generator engine (t-paliad-349)
Extract the submission generator into pkg/docforge: neutral document model
+ opaque carrier (lossless .docx), VariableResolver interface per namespace,
pluggable importer/exporter (.docx first), WYSIWYG authoring page, generic
editor UI package. 8-slice train, extract-in-place migration that protects
the b78a984 underscore fix, the placeholderRegex + data-var contracts, and
the building-block/section model.

Includes all 13 of m's decisions (5 prose-grill metaphor + 8 structured).
upc-kommentar deferred as a live consumer (it is Bun/SvelteKit/TS, zero Go);
abstractions sized for a later HTTP veneer.

m/paliad#157
2026-05-29 14:33:26 +02:00