Submission generator: improved party selector (pick from project's parties) + import-from-project affordance #109

Open
opened 2026-05-25 14:24:17 +00:00 by mAi · 1 comment
Collaborator

m's report (2026-05-25 16:22)

Add information also to the selected project or import it from there more easily. We will want an improved party selector. Cases should have their parties and we can select from them and which to mention in our submission.

A. Import-from-project affordance

When the draft is attached to a project (the normal case), pre-fill variables from the project's metadata automatically AND surface an explicit "Aus Projekt importieren" button so the lawyer can re-pull if data is stale.

Current state (per t-paliad-238 Slice A + brunel #84): SubmissionVarsService.Resolve walks project + parties + rule at preview time and emits the bag. Already auto-populates. m wants this to be MORE explicit + easier to refresh.

UI: top of the variable sidebar, a small "Aus Projekt importieren" button + last-imported-at timestamp. Clicking refreshes every project-derived variable to the current DB state. Lawyer-set overrides are NOT touched (variables.jsonb keeps its merge precedence).

B. Improved party selector — pick from project's parties

Projects can have multiple parties (claimants, defendants, intervenors, …). The submission may mention ONE primary claimant, ONE primary defendant, OR a subset of multiple. m's ask: a selector that lists every party on the project and lets the lawyer pick which to mention in this specific submission.

UI: a multi-select party picker block in the variable sidebar (replaces or augments the current {{party.claimant.name}} / {{party.defendant.name}} flat slots):

  • Display: a list of every party on the project with their role chip (Klägerin / Beklagte / Streithelfer / Patentinhaberin / Einsprechende / …)
  • Selection: checkbox per party — selected parties land in the variable bag
  • Roles: selected parties are grouped by role at substitution time. New variable shape:
    • {{party.claimants}} = comma-joined names of all selected claimants
    • {{party.defendants}} = comma-joined names of all selected defendants
    • {{party.claimant.0.name}}, {{party.defendant.0.name}}, etc. for indexed access (templates that want the primary)
    • Existing flat {{party.claimant.name}} continues to resolve to the FIRST selected claimant for backwards compat (deprecated, but kept)

Underlying data shape

  • paliad.parties already exists (project-scoped). The current submission render reads the first claimant + first defendant.
  • Selection state: add selected_parties uuid[] column to paliad.submission_drafts (default = all parties on the project, lawyer can deselect). Migration 12X.
  • SubmissionVarsService consumes selected_parties and emits the multi-shape bag.

What to do

  1. Migration: paliad.submission_drafts.selected_parties uuid[] DEFAULT '{}'::uuid[] (empty = all).
  2. Service: SubmissionVarsService.Build consumes selected_parties. If empty → include all parties on the project. If non-empty → only the selected ones.
  3. Backend: new endpoint POST /api/.../drafts/{id}/import-from-project (Idempotent re-pull; updates last_imported_at) + party selection PATCH on the existing draft endpoint.
  4. Frontend: Variable sidebar:
    • Top: "Aus Projekt importieren" button + last-imported-at timestamp.
    • Below that: party-picker block with multi-select checkboxes grouped by role.
    • Below that: existing variable list (case_number, court, today, etc.).
  5. i18n keys for the new UI.

Files most likely touched

  • internal/db/migrations/12X_submission_drafts_party_selection.up.sql
  • internal/models/models.goSubmissionDraft.SelectedParties []uuid.UUID
  • internal/services/submission_vars.go — multi-party bag shape
  • internal/services/submission_draft_service.go — selection update + import refresh
  • internal/handlers/submission_drafts.go — endpoints
  • frontend/src/submission-draft.tsx + client/submission-draft.ts — new picker block + import button
  • frontend/src/client/i18n.ts + frontend/src/i18n-keys.ts
  • frontend/src/styles/global.css — picker styling

Hard rules

  • Backward compat: existing drafts (with NULL/empty selected_parties) keep rendering as before (all parties included by default).
  • Template authors writing {{party.claimant.name}} (old shape) continue to get the first claimant.
  • go build ./... && go test ./internal/... && cd frontend && bun run build clean.
  • Branch: mai/<worker>/submission-party-selector-project-import.

Out of scope

  • A party-creation flow from inside the draft editor (lawyers create parties on the project's Parteien tab).
  • Renaming / changing party roles from the draft editor.
  • Per-party language overrides.

Reporting

mai report completed with branch + SHAs + migration slot + UX path: open a draft → see project's parties listed with role chips → de-select an intervenor → confirm preview re-renders without their name → click "Aus Projekt importieren" → confirm timestamps update + variables refresh.

## m's report (2026-05-25 16:22) > Add information also to the selected project or import it from there more easily. We will want an improved party selector. Cases should have their parties and we can select from them and which to mention in our submission. ## Scope — two related improvements to the submission draft editor ### A. Import-from-project affordance When the draft is attached to a project (the normal case), pre-fill variables from the project's metadata automatically AND surface an explicit "Aus Projekt importieren" button so the lawyer can re-pull if data is stale. Current state (per t-paliad-238 Slice A + brunel #84): `SubmissionVarsService.Resolve` walks project + parties + rule at preview time and emits the bag. Already auto-populates. m wants this to be MORE explicit + easier to refresh. UI: top of the variable sidebar, a small "Aus Projekt importieren" button + last-imported-at timestamp. Clicking refreshes every project-derived variable to the current DB state. Lawyer-set overrides are NOT touched (variables.jsonb keeps its merge precedence). ### B. Improved party selector — pick from project's parties Projects can have multiple parties (claimants, defendants, intervenors, …). The submission may mention ONE primary claimant, ONE primary defendant, OR a subset of multiple. m's ask: a selector that lists every party on the project and lets the lawyer pick which to mention in this specific submission. UI: a multi-select party picker block in the variable sidebar (replaces or augments the current `{{party.claimant.name}}` / `{{party.defendant.name}}` flat slots): - Display: a list of every party on the project with their role chip (Klägerin / Beklagte / Streithelfer / Patentinhaberin / Einsprechende / …) - Selection: checkbox per party — selected parties land in the variable bag - Roles: selected parties are grouped by role at substitution time. New variable shape: - `{{party.claimants}}` = comma-joined names of all selected claimants - `{{party.defendants}}` = comma-joined names of all selected defendants - `{{party.claimant.0.name}}`, `{{party.defendant.0.name}}`, etc. for indexed access (templates that want the primary) - Existing flat `{{party.claimant.name}}` continues to resolve to the FIRST selected claimant for backwards compat (deprecated, but kept) ### Underlying data shape - `paliad.parties` already exists (project-scoped). The current submission render reads the first claimant + first defendant. - Selection state: add `selected_parties uuid[]` column to `paliad.submission_drafts` (default = all parties on the project, lawyer can deselect). Migration 12X. - `SubmissionVarsService` consumes `selected_parties` and emits the multi-shape bag. ## What to do 1. **Migration**: `paliad.submission_drafts.selected_parties uuid[] DEFAULT '{}'::uuid[]` (empty = all). 2. **Service**: `SubmissionVarsService.Build` consumes `selected_parties`. If empty → include all parties on the project. If non-empty → only the selected ones. 3. **Backend**: new endpoint `POST /api/.../drafts/{id}/import-from-project` (Idempotent re-pull; updates `last_imported_at`) + party selection PATCH on the existing draft endpoint. 4. **Frontend**: Variable sidebar: - Top: "Aus Projekt importieren" button + last-imported-at timestamp. - Below that: party-picker block with multi-select checkboxes grouped by role. - Below that: existing variable list (case_number, court, today, etc.). 5. **i18n** keys for the new UI. ## Files most likely touched - `internal/db/migrations/12X_submission_drafts_party_selection.up.sql` - `internal/models/models.go` — `SubmissionDraft.SelectedParties []uuid.UUID` - `internal/services/submission_vars.go` — multi-party bag shape - `internal/services/submission_draft_service.go` — selection update + import refresh - `internal/handlers/submission_drafts.go` — endpoints - `frontend/src/submission-draft.tsx` + `client/submission-draft.ts` — new picker block + import button - `frontend/src/client/i18n.ts` + `frontend/src/i18n-keys.ts` - `frontend/src/styles/global.css` — picker styling ## Hard rules - **Backward compat**: existing drafts (with NULL/empty `selected_parties`) keep rendering as before (all parties included by default). - Template authors writing `{{party.claimant.name}}` (old shape) continue to get the first claimant. - `go build ./... && go test ./internal/... && cd frontend && bun run build` clean. - Branch: `mai/<worker>/submission-party-selector-project-import`. ## Out of scope - A party-creation flow from inside the draft editor (lawyers create parties on the project's Parteien tab). - Renaming / changing party roles from the draft editor. - Per-party language overrides. ## Reporting `mai report completed` with branch + SHAs + migration slot + UX path: open a draft → see project's parties listed with role chips → de-select an intervenor → confirm preview re-renders without their name → click "Aus Projekt importieren" → confirm timestamps update + variables refresh.
mAi self-assigned this 2026-05-25 14:24:17 +00:00
Author
Collaborator

SHIPPED on mai/artemis/gitster-submission @ 4fc3005

Commit

What's in

Schema (mig 131 — slot reservation per paliadin head-up, m/paliad#96 takes 129, m/paliad#108 takes 130):

  • paliad.submission_drafts.selected_parties uuid[] DEFAULT '{}'::uuid[] — empty = include every party (legacy default), non-empty = restrict to subset.
  • paliad.submission_drafts.last_imported_at timestamptz NULL — bumped on each "Aus Projekt importieren" click.

Backend:

  • SubmissionVarsContext gains SelectedParties; filterPartiesBySelection restricts before role bucketing.
  • addPartyVars emits three coexisting forms per role:
    • Comma-joined: {{parties.claimants}}, {{parties.claimants.representatives}} (skips empty reps from join), same for defendants / others.
    • Indexed: {{parties.claimant.0.name}}, {{parties.claimant.0.representative}}, {{parties.defendant.0.name}}, etc.
    • Flat legacy: {{parties.claimant.name}} resolves to the first selected claimant — kept forever per the issue's backward-compat contract.
  • German role strings (Kläger / Klägerin / Beklagter / Beklagte) bucket the same as English equivalents.
  • SubmissionDraftService.ImportFromProject strips overrides for project.* / parties.* / deadline.* / procedural_event.* / rule.* prefixes and bumps last_imported_at. firm.* / today.* / user.* overrides survive (those aren't project-derived). Rejects project-less drafts with ErrInvalidInput → 400.
  • New endpoint: POST /api/submission-drafts/{id}/import-from-project.
  • DraftPatch + project-scoped PATCH + global PATCH all accept selected_parties.
  • submissionDraftView now ships available_parties so the editor renders the picker in one round-trip (no extra GET /api/projects/{id}/parties needed).

Frontend (submission-draft.tsx + client/submission-draft.ts):

  • New "Aus Projekt importieren" button + last-imported-at timestamp row at the top of the variable sidebar — hidden when the draft has no project.
  • New multi-select party picker block — checkbox per party, grouped by role (Klägerinnen / Beklagte / Weitere Parteien), with role chip + representative subtitle.
  • Empty selection (the default) renders with every party pre-checked so the lawyer sees what's currently being mentioned — deselecting reverts to the actual non-empty subset persisted to selected_parties. Re-checking everything writes back as [] so a future party added to the project gets picked up automatically.
  • Autosave on selection change; preview re-renders in lockstep.
  • 3 new i18n keys (DE+EN): submissions.draft.import.button, submissions.draft.parties.title, submissions.draft.parties.hint.
  • ~90 LoC of CSS for the picker + import row in global.css.

Tests

6 new unit tests in internal/services/submission_vars_parties_test.go:

  • TestAddPartyVars_MultiPartyMixedRoles — pins all 3 forms (joined, indexed, flat) with mixed roles + empty representatives.
  • TestAddPartyVars_GermanRoleStrings — confirms Klägerin / Beklagter bucket correctly.
  • TestAddPartyVars_BackwardCompatFlatAliasResolvesFirstRow — pins the m/paliad#109 backward-compat contract.
  • TestFilterPartiesBySelection_EmptyMeansAll — both nil and []uuid.UUID{} mean "include every party".
  • TestFilterPartiesBySelection_NonEmptyRestricts — subset selection preserves input order.
  • TestIsProjectDerivedKey — covers the import-from-project policy (project/parties/deadline/procedural_event/rule are derived; firm/today/user survive).

Build hygiene

  • go build ./...
  • go vet ./...
  • go test -short ./internal/... (6 new tests pass, no regressions)
  • bun run build (2876 i18n keys, data-i18n scan clean)
  • gofmt -w applied to touched files.

UX path (after head merge)

Open any project with multiple parties → Schriftsätze → pick a draft → see the party picker in the sidebar with role chips → de-select an intervenor → confirm preview re-renders without their name in {{parties.others}} → click "Aus Projekt importieren" → confirm timestamp updates and project-derived variables refresh (user-typed firm/user overrides survive).

Out of scope (per issue body)

  • Party-creation flow from inside the draft editor (lawyers still create parties on the project's /projects/{id}/parties tab).
  • Renaming / changing party roles from the draft editor.
  • Per-party language overrides.

Not self-merged. Awaiting head.

## SHIPPED on `mai/artemis/gitster-submission` @ `4fc3005` [Commit](https://mgit.msbls.de/m/paliad/commit/4fc3005) ### What's in **Schema (mig 131 — slot reservation per paliadin head-up, m/paliad#96 takes 129, m/paliad#108 takes 130):** - `paliad.submission_drafts.selected_parties uuid[] DEFAULT '{}'::uuid[]` — empty = include every party (legacy default), non-empty = restrict to subset. - `paliad.submission_drafts.last_imported_at timestamptz NULL` — bumped on each "Aus Projekt importieren" click. **Backend:** - `SubmissionVarsContext` gains `SelectedParties`; `filterPartiesBySelection` restricts before role bucketing. - `addPartyVars` emits **three coexisting forms per role**: - Comma-joined: `{{parties.claimants}}`, `{{parties.claimants.representatives}}` (skips empty reps from join), same for `defendants` / `others`. - Indexed: `{{parties.claimant.0.name}}`, `{{parties.claimant.0.representative}}`, `{{parties.defendant.0.name}}`, etc. - Flat legacy: `{{parties.claimant.name}}` resolves to the **first** selected claimant — kept forever per the issue's backward-compat contract. - German role strings (Kläger / Klägerin / Beklagter / Beklagte) bucket the same as English equivalents. - `SubmissionDraftService.ImportFromProject` strips overrides for `project.*` / `parties.*` / `deadline.*` / `procedural_event.*` / `rule.*` prefixes and bumps `last_imported_at`. `firm.*` / `today.*` / `user.*` overrides survive (those aren't project-derived). Rejects project-less drafts with `ErrInvalidInput` → 400. - New endpoint: `POST /api/submission-drafts/{id}/import-from-project`. - `DraftPatch` + project-scoped PATCH + global PATCH all accept `selected_parties`. - `submissionDraftView` now ships `available_parties` so the editor renders the picker in one round-trip (no extra `GET /api/projects/{id}/parties` needed). **Frontend (`submission-draft.tsx` + `client/submission-draft.ts`):** - New "Aus Projekt importieren" button + last-imported-at timestamp row at the top of the variable sidebar — hidden when the draft has no project. - New multi-select party picker block — checkbox per party, grouped by role (Klägerinnen / Beklagte / Weitere Parteien), with role chip + representative subtitle. - Empty selection (the default) renders with every party pre-checked so the lawyer sees what's currently being mentioned — deselecting reverts to the actual non-empty subset persisted to `selected_parties`. Re-checking everything writes back as `[]` so a future party added to the project gets picked up automatically. - Autosave on selection change; preview re-renders in lockstep. - 3 new i18n keys (DE+EN): `submissions.draft.import.button`, `submissions.draft.parties.title`, `submissions.draft.parties.hint`. - ~90 LoC of CSS for the picker + import row in `global.css`. ### Tests 6 new unit tests in `internal/services/submission_vars_parties_test.go`: - `TestAddPartyVars_MultiPartyMixedRoles` — pins all 3 forms (joined, indexed, flat) with mixed roles + empty representatives. - `TestAddPartyVars_GermanRoleStrings` — confirms `Klägerin` / `Beklagter` bucket correctly. - `TestAddPartyVars_BackwardCompatFlatAliasResolvesFirstRow` — pins the m/paliad#109 backward-compat contract. - `TestFilterPartiesBySelection_EmptyMeansAll` — both `nil` and `[]uuid.UUID{}` mean "include every party". - `TestFilterPartiesBySelection_NonEmptyRestricts` — subset selection preserves input order. - `TestIsProjectDerivedKey` — covers the import-from-project policy (project/parties/deadline/procedural_event/rule are derived; firm/today/user survive). ### Build hygiene - `go build ./...` ✅ - `go vet ./...` ✅ - `go test -short ./internal/...` ✅ (6 new tests pass, no regressions) - `bun run build` ✅ (2876 i18n keys, data-i18n scan clean) - `gofmt -w` applied to touched files. ### UX path (after head merge) Open any project with multiple parties → Schriftsätze → pick a draft → see the party picker in the sidebar with role chips → de-select an intervenor → confirm preview re-renders without their name in `{{parties.others}}` → click "Aus Projekt importieren" → confirm timestamp updates and project-derived variables refresh (user-typed firm/user overrides survive). ### Out of scope (per issue body) - Party-creation flow from inside the draft editor (lawyers still create parties on the project's `/projects/{id}/parties` tab). - Renaming / changing party roles from the draft editor. - Per-party language overrides. Not self-merged. Awaiting head.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#109
No description provided.