docs: ratify Q1-Q12 — submission generator v2 design final (m/paliad#141)
All 12 §11 design questions ratified by m on 2026-05-26 via AskUserQuestion (paliadin-authorised override per instruction msg #2391). Picks matching inventor recommendations (9 of 12): Q1 separate submission_sections table Q3 Gitea-backed body + thin DB row Q4 contentEditable + Markdown + in-house serializer Q5 section anchors + in-house MD->OOXML walker Q7 split content_md_de + content_md_en from day 1 Q8 Go map for per-submission_code section defaults Q9 4 visibility tiers (private/team/firm/global) Q11 collapsed preview pane by default Q12 moot (superseded by Q2 simplification) Deviations from recommendation (3 of 12): Q2 SIMPLIFY further — m: "sounds overengineered". Building blocks become plain text paste sources. No building_block_id column on sections, no _versions table referenced from sections, no refresh-from-library affordance. Slice G dropped. Q6 Auto-upgrade all 11 existing drafts at mig-148 apply time (not opt-in per draft). v1 fallback render path stays compiled in. Q10 *_auto kind removed. Caption/letterhead/signature sections are regular prose rows seeded with bag-driven Markdown; lawyer can edit/hide. Untouched drafts export identically to v1. Body sections updated inline (§4.3 schema, §4.4 BB tables, §6.3 seeding, §8.3+8.4 BB insert, slice plan A/C/G, §11 ratification notes, §14 risks #8+11, §17+18 acceptance + gate). §11 retains the historical recommendation matrix. Status: ALL DESIGN QUESTIONS RATIFIED — design doc final, ready for Slice A coder shift. Inventor parks per hard gate. Head decides hire. t-paliad-312
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
**Task:** t-paliad-312
|
||||
**Issue:** m/paliad#141
|
||||
**Branch:** `mai/cronus/inventor-prd-for`
|
||||
**Status:** DESIGN READY FOR REVIEW
|
||||
**Status:** ALL DESIGN QUESTIONS RATIFIED — final, ready for Slice A coder shift
|
||||
**Prior art:**
|
||||
- `docs/design-submission-generator-2026-05-19.md` (t-paliad-215, Slice 1: template-merge engine, fallback chain, audit shape)
|
||||
- `docs/design-submission-page-2026-05-22.md` (t-paliad-238, Slice A: dedicated draft editor at `/projects/{id}/submissions/{code}/draft`, sidebar + preview, autosave)
|
||||
@@ -14,6 +14,60 @@ This design deepens — it does **not** replace — both prior docs. Every contr
|
||||
|
||||
---
|
||||
|
||||
## § m's decisions (2026-05-26)
|
||||
|
||||
m ratified the 12 §11 questions via AskUserQuestion (paliadin override per instruction msg #2391 — "interactive ratification, no doc round-trip"). Picks below; where m deviated from the inventor's recommendation, the design body has been updated inline. The original §11 entries stay as the historical record.
|
||||
|
||||
| # | Topic | m's pick | vs Recommended? |
|
||||
|---|---|---|---|
|
||||
| Q1 | Section content storage | Separate `submission_sections` table | ✅ matches |
|
||||
| Q2 | Building block insert semantics | **SIMPLIFY** — m: "I don't even understand the question, that sounds a bit overengineered" → drop lineage/refresh machinery. Building blocks are plain text snippets pasted into sections. No `building_block_id`, no `_versions` table, no diff/merge UI. | ⚠️ rescope |
|
||||
| Q3 | Base storage | Gitea-backed body + thin DB row | ✅ matches |
|
||||
| Q4 | Editor surface | contentEditable + minimal toolbar + Markdown source-of-truth, in-house serializer | ✅ matches |
|
||||
| Q5 | Render pipeline | Section anchors in base + in-house MD → OOXML walker (pure Go) | ✅ matches |
|
||||
| Q6 | Migration of existing 11 drafts | **Auto-upgrade all on migration apply** — every draft auto-seeded with firm-default base + sections derived from spec. v1 fallback path stays compiled in for safety. | ⚠️ deviation — risk noted in §14 |
|
||||
| Q7 | Multi-language storage | Split `content_md_de` + `content_md_en` from day 1 | ✅ matches |
|
||||
| Q8 | Section defaults per submission_code | Go map (`submission_section_defaults.go`) | ✅ matches |
|
||||
| Q9 | Building block visibility tiers | 4 tiers (private / team / firm / global) | ✅ matches |
|
||||
| Q10 | `*_auto` section editability | **Fully editable** — lawyer can override auto-rendered Rubrum / letterhead / signature text. No special read-only kind. | ⚠️ deviation — risk noted in §14 |
|
||||
| Q11 | Preview pane default | Collapsed by default, `[Vorschau ▾]` toggle expands | ✅ matches |
|
||||
| Q12 | Refresh-from-library trigger | **Moot** — superseded by Q2's simplification (no lineage tracked, so no "refresh" affordance exists). | — |
|
||||
|
||||
### Rescope: Q2 + Q12 simplification (combined effect)
|
||||
|
||||
The Q2 + Q12 picks collapse the building-block machinery to its simplest form:
|
||||
|
||||
- **No** `building_block_id` / `building_block_version` columns on `submission_sections`.
|
||||
- **No** `submission_building_block_versions` table.
|
||||
- **No** "refresh from library" button, no diff view, no Slice G.
|
||||
- Building blocks become a one-way clipboard: the lawyer browses the library, clicks "Einfügen", the text appears at the cursor, end of story. The section row keeps no record of where the text came from.
|
||||
- The library admin editor (Slice C) still has its own version history (retention-20, like email templates) for audit + accidental-delete recovery — that's an internal admin concern, not a per-section concept.
|
||||
|
||||
### Rescope: Q6 auto-upgrade
|
||||
|
||||
The migration applying `submission_sections` + `submission_drafts.base_id` auto-seeds every existing row:
|
||||
|
||||
- `base_id` set to the firm default base for the draft's proceeding family (matched at migration time via `submission_codes_shape` / proceeding lookup).
|
||||
- `submission_sections` rows seeded from `base.section_spec.defaults` per draft, with empty `content_md_de`/`content_md_en` for `prose`/`requests`/`evidence` kinds.
|
||||
- `composer_meta` populated with the default `section_order` so the editor paints immediately on next open.
|
||||
- v1 fallback render path stays compiled in (gate: `base_id IS NULL OR no sections rows`) as a safety net should the auto-seed miss a draft.
|
||||
|
||||
**Risk:** the lawyer's next visit to a previously-edited draft shows the new Composer UI. The lawyer's existing `variables` jsonb overrides survive untouched; section content starts empty. Lawyer experiences this as "my variable fills are still here, plus a new section editor I can ignore until I want to use it". Acceptable per m. Documented in §14 risk #8.
|
||||
|
||||
### Rescope: Q10 fully-editable `*_auto` sections
|
||||
|
||||
`*_auto` kinds disappear from the design. All sections become `prose` (default seeded from base + bag where the base supplies canonical OOXML), `requests`, or `evidence`. The base.docx still ships canonical Rubrum / letterhead / signature OOXML inside each section's anchor — but on first edit the lawyer's content_md replaces it.
|
||||
|
||||
UX implication: on draft open, "Rubrum" / "Briefkopf" / "Unterschrift" sections show the auto-rendered content as initial text inside the editor. The lawyer can leave it alone (renders identically to old `*_auto` behaviour at export), edit it (their MD replaces the base's OOXML at render), or hide it (`included=false`).
|
||||
|
||||
**Risk:** the lawyer types a party name into the Rubrum manually that doesn't match the variable bag's `{{parties.claimant.0.name}}`. The exported `.docx` shows the typed name — variable substitution is a no-op against literal text. Acceptable: the lawyer is in control, this is a feature not a bug. Documented in §14 risk #11.
|
||||
|
||||
### Status
|
||||
|
||||
Design final. Inventor parks per hard gate. Head decides Slice A coder shift.
|
||||
|
||||
---
|
||||
|
||||
## §0 TL;DR
|
||||
|
||||
Today (v1): one Word `.docx` template per `submission_code`, picked from Gitea via a fallback chain, merged with a flat `{{placeholder}}` variable bag built from project + parties + deadline + user + firm state. The lawyer's only authoring surface is the sidebar — they fill in variables, see a read-only HTML preview, export to `.docx`, and finish in Word.
|
||||
@@ -156,14 +210,12 @@ CREATE TABLE paliad.submission_sections (
|
||||
draft_id uuid NOT NULL REFERENCES paliad.submission_drafts(id) ON DELETE CASCADE,
|
||||
section_key text NOT NULL, -- 'requests', 'facts', 'legal_argument', …
|
||||
order_index int NOT NULL, -- lawyer-orderable
|
||||
kind text NOT NULL, -- 'prose' | 'requests' | 'evidence' | 'caption_auto' | 'letterhead_auto' | 'signature_auto'
|
||||
kind text NOT NULL, -- 'prose' | 'requests' | 'evidence' (per Q10: no *_auto kind)
|
||||
label_de text NOT NULL, -- lawyer-renameable
|
||||
label_en text NOT NULL,
|
||||
included bool NOT NULL DEFAULT true,
|
||||
content_md_de text NOT NULL DEFAULT '', -- Markdown source; empty for *_auto kinds
|
||||
content_md_de text NOT NULL DEFAULT '', -- Markdown source. Seeded with base's canonical OOXML rendering for caption/letterhead/signature; editable.
|
||||
content_md_en text NOT NULL DEFAULT '',
|
||||
building_block_id uuid REFERENCES paliad.submission_building_blocks(id) ON DELETE SET NULL,
|
||||
building_block_version int, -- snapshot version at insert time
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
|
||||
@@ -177,13 +229,17 @@ ALTER TABLE paliad.submission_sections ENABLE ROW LEVEL SECURITY;
|
||||
-- Visibility flows through draft_id → submission_drafts → can_see_project + owner-scoped.
|
||||
```
|
||||
|
||||
`section_key` is a stable identifier (English slug) the render pipeline keys off. `kind` distinguishes free-prose sections (`prose`, `requests`, `evidence`) from sections rendered automatically from the variable bag (`*_auto`) — those can be hidden but not edited as text. `label_de` / `label_en` start from the section spec defaults but can be renamed per draft.
|
||||
`section_key` is a stable identifier (English slug) the render pipeline keys off. `kind` distinguishes prose, anträge, and evidence-style content for the editor's affordances (e.g. Anträge sections may auto-number; evidence sections show a "Beweis: " inline prefix). Per Q10, there is no read-only `*_auto` kind — every section is lawyer-editable, but caption/letterhead/signature sections are *seeded* with the base's canonical OOXML rendering so leaving them untouched produces the same output v1 lawyers see today.
|
||||
|
||||
`label_de` / `label_en` start from the section spec defaults but can be renamed per draft.
|
||||
|
||||
`content_md_de` + `content_md_en` are both columns from the start (Slice F is not deferred — see §11 Q7) so a draft is bilingual-by-construction; the active rendering picks the column matching `submission_drafts.language`.
|
||||
|
||||
`building_block_id` retains lineage but the row's content is the snapshot at insertion time (§8.3 copy-on-insert).
|
||||
Per Q2: there is no `building_block_id` lineage. Building blocks are plain text snippets; once inserted, the snippet's prose belongs to the section row, full stop.
|
||||
|
||||
### §4.4 New — `submission_building_blocks` + `_versions`
|
||||
### §4.4 New — `submission_building_blocks`
|
||||
|
||||
Per Q2: building blocks are plain text snippets. No `_versions` companion table exposed to the section side; admin-side version history lives in a private `_admin_versions` table for accidental-delete recovery only (mirrors the email-templates retention=20 pattern).
|
||||
|
||||
```sql
|
||||
CREATE TABLE paliad.submission_building_blocks (
|
||||
@@ -201,7 +257,6 @@ CREATE TABLE paliad.submission_building_blocks (
|
||||
author_id uuid REFERENCES paliad.users(id),
|
||||
visibility text NOT NULL, -- 'private' | 'team' | 'firm' | 'global'
|
||||
is_published bool NOT NULL DEFAULT false,
|
||||
version int NOT NULL DEFAULT 1,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz,
|
||||
@@ -209,17 +264,15 @@ CREATE TABLE paliad.submission_building_blocks (
|
||||
UNIQUE (slug, firm)
|
||||
);
|
||||
|
||||
CREATE TABLE paliad.submission_building_block_versions (
|
||||
-- Admin-side audit only; not referenced from submission_sections.
|
||||
CREATE TABLE paliad.submission_building_block_admin_versions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
building_block_id uuid NOT NULL REFERENCES paliad.submission_building_blocks(id) ON DELETE CASCADE,
|
||||
version int NOT NULL,
|
||||
content_md_de text NOT NULL,
|
||||
content_md_en text NOT NULL,
|
||||
edited_by uuid REFERENCES paliad.users(id),
|
||||
note text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
|
||||
UNIQUE (building_block_id, version)
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX submission_building_blocks_section_visibility_idx
|
||||
@@ -234,7 +287,7 @@ Visibility tiers (mirror the team-visibility model elsewhere in paliad):
|
||||
- `firm` — visible to every user whose `email` ends in any of `ALLOWED_EMAIL_DOMAINS` and whose `branding.Name` matches the block's `firm`.
|
||||
- `global` — visible to every authenticated user.
|
||||
|
||||
Versioning: same retention-20 policy as email templates (`internal/services/email_template_service.go`). Each Save appends a `_versions` row + GCs to 20 in-transaction.
|
||||
Versioning: same retention-20 policy as email templates (`internal/services/email_template_service.go`). Each admin Save appends a `_admin_versions` row + GCs to 20 in-transaction. This is an admin-side concern — sections never reference it. The lawyer's section row is unaware which version of which library block its content originated from.
|
||||
|
||||
### §4.5 Migration count and order
|
||||
|
||||
@@ -242,12 +295,12 @@ One migration per concern (atomic, drift-safe), suggested in this order — code
|
||||
|
||||
| Slot | Purpose |
|
||||
|---|---|
|
||||
| 146 | `ALTER TABLE submission_drafts ADD COLUMN base_id uuid REFERENCES submission_bases(id); ADD COLUMN composer_meta jsonb NOT NULL DEFAULT '{}'::jsonb;` (must come after `submission_bases` exists — alternative: bundle 146+147) |
|
||||
| 147 | `CREATE TABLE submission_bases` + RLS + seed rows for `hlc-letterhead`, `neutral`. (Specialist bases land in Slice E migrations.) |
|
||||
| 148 | `CREATE TABLE submission_sections` + RLS + indexes. |
|
||||
| 149 | `CREATE TABLE submission_building_blocks` + `_versions` + RLS + indexes. |
|
||||
| 146 | `CREATE TABLE submission_bases` + RLS + seed rows for `hlc-letterhead`, `neutral`. (Specialist bases land in Slice E migrations.) |
|
||||
| 147 | `ALTER TABLE submission_drafts ADD COLUMN base_id uuid REFERENCES submission_bases(id); ADD COLUMN composer_meta jsonb NOT NULL DEFAULT '{}'::jsonb;` |
|
||||
| 148 | `CREATE TABLE submission_sections` + RLS + indexes. **Auto-seed all 11 existing drafts** (per Q6) — for each draft: set `base_id` from firm default for the proceeding family; INSERT one `submission_sections` row per default in the base's `section_spec.defaults`, copying `seed_md_de` / `seed_md_en` into `content_md_de` / `content_md_en`; populate `composer_meta.section_order`. Idempotent (ON CONFLICT DO NOTHING). |
|
||||
| 149 | `CREATE TABLE submission_building_blocks` + `submission_building_block_admin_versions` + RLS + indexes (per Q2, no lineage column on `submission_sections`). |
|
||||
|
||||
The 146 column add must reference the bases table — practically, 147 (create bases) ships first and 146 becomes the next slot. Slot numbers are flexible; the coder picks contiguous free slots at impl time.
|
||||
Slot numbers are flexible; the coder picks contiguous free slots at impl time. The auto-seed (mig 148) is the migration-time write that Q6 ratified — every existing draft gains its Composer surface at apply.
|
||||
|
||||
---
|
||||
|
||||
@@ -353,15 +406,17 @@ var sectionOverrides = map[string][]sectionAdjustment{
|
||||
|
||||
This is intentionally code-driven (not DB-driven) for v2.0 (see §11 Q8). The override applies once at draft creation; subsequent lawyer reorders win.
|
||||
|
||||
### §6.3 `*_auto` sections — content comes from the bag, not the editor
|
||||
### §6.3 Caption / Letterhead / Signature — seeded but editable (per Q10)
|
||||
|
||||
Three section kinds render entirely from the base + variable bag, with no user-editable content body:
|
||||
The "auto" concept is gone. Caption, letterhead, and signature sections are regular `prose`-kind rows. Their distinguishing trait is the *seed*: on draft create, their `content_md_*` is pre-filled with a Markdown rendering of the canonical bag-driven content (so a draft that's untouched after create exports identically to v1's auto-rendered Rubrum / letterhead / signature).
|
||||
|
||||
- **`letterhead_auto`** — typically a `.docx` header part (HL Patents Style header1.xml) is the actual letterhead; the in-body section is either empty or a small "Schriftsatz von:" preamble using `{{firm.name}}`, `{{user.display_name}}`, etc. The lawyer toggles include/exclude but doesn't type text here.
|
||||
- **`caption_auto`** — the "Rubrum" block. Renders from `{{parties.claimant.0.name}}`, `{{parties.defendant.0.name}}`, `{{project.case_number}}`, `{{project.court}}` etc. via a fixed micro-template embedded in the base (paragraphs using HLpat-Table-Recitals-Party styles). Lawyer toggles include/exclude.
|
||||
- **`signature_auto`** — same shape. Uses `{{firm.signature_block}}` + `{{user.display_name}}`.
|
||||
- **Letterhead** — typically a `.docx` header part (HL Patents Style header1.xml) is the actual letterhead. The in-body letterhead section seeds with a "Schriftsatz von:" preamble using `{{firm.name}}`, `{{user.display_name}}`. Lawyer can edit, hide, or rename freely.
|
||||
- **Caption (Rubrum)** — seeds with paragraphs using `{{parties.claimant.0.name}}`, `{{parties.defendant.0.name}}`, `{{project.case_number}}`, `{{project.court}}`. Lawyer can edit the prose around the placeholders (e.g. add an intervener block) or override entirely.
|
||||
- **Signature** — seeds with `{{firm.signature_block}}` + `{{user.display_name}}`.
|
||||
|
||||
The base's section anchor for each `*_auto` section contains the OOXML for the canonical block. When the section is `included=false`, the entire `{{#section:KEY}} … {{/section:KEY}}` slot is dropped at render time.
|
||||
When `included=false`, the section is dropped at render time. When `included=true` and the lawyer hasn't touched the content, the seeded Markdown renders identically to v1's auto-block — same placeholders resolve, same OOXML output. When the lawyer edits, their Markdown wins.
|
||||
|
||||
**Seeding mechanism:** the base's `section_spec.defaults` declares a `seed_md_de` / `seed_md_en` per section. On draft create, the section row's `content_md_*` is initialised from the seed. Subsequent base swaps do NOT re-seed (the lawyer may have edited the content already; re-seeding would clobber).
|
||||
|
||||
### §6.4 Custom sections
|
||||
|
||||
@@ -398,10 +453,12 @@ The main body grows from a single read-only preview pane to a **section stack**:
|
||||
│ Basis: [HLC-Briefkopf ▾] [Basis wechseln…] │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ 1. Briefkopf [Auto] [☐ verstecken] │
|
||||
│ │ 1. Briefkopf [+ Baustein] [⋮] │ │
|
||||
│ │ (seeded — bearbeitbar) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ 2. Rubrum [Auto] [☐ verstecken] │
|
||||
│ │ 2. Rubrum [+ Baustein] [⋮] │ │
|
||||
│ │ (seeded — bearbeitbar) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ 3. Einleitung [+ Baustein] [⋮] │ │
|
||||
@@ -456,9 +513,9 @@ In section content, the lawyer types `{{` and an inline picker drops below the c
|
||||
|
||||
`+ Baustein einfügen` button → modal listing visible blocks filtered to `section_key = <this section's key>` AND `proceeding_family ∈ {project family, NULL}` AND visibility within reach. Each row shows title, description, first 200 chars of `content_md_<lang>`. Click → block's full `content_md_<lang>` is appended to current cursor position; section row gets `building_block_id` + `building_block_version` stamped.
|
||||
|
||||
If the lawyer already has content in the section, the insert is appended with a `\n\n` separator. The lawyer can drag/edit afterwards.
|
||||
If the lawyer already has content in the section, the insert is appended at the cursor with a `\n\n` separator. The lawyer can drag/edit afterwards.
|
||||
|
||||
A small badge in the section header — "Bausteinquelle: <block title> (Version <N>)" — surfaces lineage. Clicking the badge opens a one-step "Mit Bibliotheksversion abgleichen" diff view (Slice C+).
|
||||
No lineage badge, no "refresh from library" surface — see §8.4. The picker is a paste source, nothing more.
|
||||
|
||||
### §7.6 Autosave
|
||||
|
||||
@@ -484,25 +541,19 @@ New admin page `/admin/submission-blocks` (mirrors `/admin/email-templates` shap
|
||||
|
||||
Non-admin users get `/blocks` (personal) — same UI but filtered to `visibility = 'private'` and their authored rows. Private → published bumps visibility tier; tier upgrade routes the block past an admin moderation step if going `firm` or `global`.
|
||||
|
||||
### §8.3 Copy-on-insert + lineage
|
||||
### §8.3 Insert mechanics — plain text paste (per Q2)
|
||||
|
||||
Insert mechanics:
|
||||
Insert is dumb-paste: clone the block's `content_md_<active_lang>` into the section row's `content_md_<lang>` at the cursor offset. End of story. No `building_block_id` stamp, no version snapshot, no lineage tracking.
|
||||
|
||||
1. Render Markdown copy of block's `content_md_<active_lang>` into `submission_sections.content_md_<lang>` at the cursor offset.
|
||||
2. Stamp `submission_sections.building_block_id = <block.id>` and `building_block_version = <block.version>`.
|
||||
3. If the section already had a `building_block_id` pinned: do NOT override the lineage — instead, append the new block's content with a `\n\n---\n\n` separator and clear the lineage stamp (mixed origin).
|
||||
Rationale (m's call): the building-block-as-text-snippet mental model maps to how lawyers already think about boilerplate. They don't think about "this passage is a live reference to a library entry" — they think "I pasted this in, I might edit it". The simpler design honours that intuition.
|
||||
|
||||
Rationale: the lawyer's edits never get clobbered by library updates. Lineage stays so we can show "this was inserted from BB-Antrag-Standardfassung v3 — Library is now at v5, [refresh?]".
|
||||
Consequence: library edits do NOT propagate to in-flight drafts (by construction — no link exists). Lawyer who wants the latest version re-opens the picker and inserts again. That's the trade.
|
||||
|
||||
### §8.4 Refresh-from-library
|
||||
### §8.4 No refresh-from-library affordance (per Q12, superseded by Q2)
|
||||
|
||||
A per-section button "Aus Bibliothek aktualisieren" (only shown when `building_block_id` is set AND the library version exceeds the snapshot version). Click flow:
|
||||
There is no "Aus Bibliothek aktualisieren" button. There is no diff view. The lawyer's content is the lawyer's content; the library is a paste source. If the lawyer wants to re-sync, they delete the section text and re-insert from the library.
|
||||
|
||||
1. Render a diff view (text-level) of "current section content" vs "library version N+M content_md_<lang>".
|
||||
2. Lawyer picks: (a) keep current, (b) overwrite with library version, (c) merge — opens a side-by-side editor with conflict markers.
|
||||
3. On overwrite/merge: bump `building_block_version` to current.
|
||||
|
||||
Defaults to "off" — no automatic propagation. The lawyer's call always.
|
||||
Library admins still get the `_admin_versions` audit trail for accidental-delete recovery on the library side (§4.4) — that's an internal admin concern, not a per-section UX.
|
||||
|
||||
### §8.5 Visibility tiers — concrete examples
|
||||
|
||||
@@ -647,6 +698,8 @@ Alternatives:
|
||||
|
||||
Why default: the lawyer's edits never get clobbered. The "library was updated, refresh?" affordance is opt-in via the lineage. Predictable; matches Word's "Paste as plain text" mental model.
|
||||
|
||||
> **m's ratification (2026-05-26):** "I don't even understand the question, that sounds a bit overengineered." → **SIMPLIFY** further than the recommendation. No `building_block_id` column on sections at all. Building blocks are plain text snippets pasted into sections; once pasted, the prose belongs to the section, no link back to the library. This dissolves Q12 (no refresh-from-library) and Slice G (no diff/merge UI).
|
||||
|
||||
### Q3 — Base storage
|
||||
|
||||
**Default: Gitea-backed body + thin DB row** (`submission_bases` row points at `gitea_path`).
|
||||
@@ -689,6 +742,8 @@ Alternatives:
|
||||
|
||||
Why default: zero blast radius on existing rows; explicit consent on first edit. Lawyer can defer indefinitely.
|
||||
|
||||
> **m's ratification (2026-05-26):** picked **(a) Auto-upgrade**. The migration walks every existing draft in-transaction, sets `base_id` from the firm default for its proceeding family, and writes seeded section rows. v1 fallback path stays compiled in as a safety net. Risk noted in §14 #8.
|
||||
|
||||
### Q7 — Multi-language storage
|
||||
|
||||
**Default: split `content_md_de` + `content_md_en` columns from day one.**
|
||||
@@ -729,6 +784,8 @@ Alternatives:
|
||||
|
||||
Why default: keeps the auto-rendered blocks consistent across drafts in the firm (no per-draft Rubrum drift) while letting the lawyer drop them when they don't apply (e.g., a "Schutzschrift" might omit the Rubrum block).
|
||||
|
||||
> **m's ratification (2026-05-26):** picked **(a) Fully editable**. The `*_auto` kind is removed. Caption, letterhead, and signature sections are regular `prose` rows seeded with bag-driven Markdown (so an untouched draft exports identically to v1). Lawyer edit replaces the seed at render time. Risk noted in §14 #11.
|
||||
|
||||
### Q11 — Preview pane
|
||||
|
||||
**Default: collapsed by default in v2 (in-place editor IS the surface).** Toggle `[Vorschau ▾]` opens a read-only HTML render below the section stack.
|
||||
@@ -749,6 +806,8 @@ Alternatives:
|
||||
|
||||
Why default: opt-in keeps lawyer in control; the surfaced affordance triggers only when there's a real update.
|
||||
|
||||
> **m's ratification (2026-05-26):** **moot** — Q2's simplification removed the lineage tracking this question presupposed. No "refresh from library" affordance exists. If the lawyer wants the latest version of a block, they delete the section text and re-insert from the picker.
|
||||
|
||||
---
|
||||
|
||||
## §12 Slice plan
|
||||
@@ -758,18 +817,18 @@ Each slice independently shippable. Coder picks up the next slice only after the
|
||||
### Slice A — Base picker (read-only) and section list shell
|
||||
|
||||
**Scope:**
|
||||
- Migrations: `submission_bases` table, seed `hlc-letterhead` + `neutral` rows pointing at existing `.docx` files; `submission_sections` table; `submission_drafts.base_id` + `composer_meta` columns.
|
||||
- Backend: `BaseService` (list, get-by-slug, get-default-for-code). `SubmissionDraftService.Create` seeds `base_id` + section rows.
|
||||
- Frontend: sidebar picker "Vorlagenbasis" with the seeded bases. Section list rendered read-only above the existing preview (no edit yet).
|
||||
- Render: unchanged — still uses v1 `resolveSubmissionTemplate` chain. `base_id` is informational.
|
||||
- **Ships:** lawyer sees the base concept land, no behavior change.
|
||||
- Migrations: `submission_bases` table, seed `hlc-letterhead` + `neutral` rows pointing at existing `.docx` files; `submission_sections` table; `submission_drafts.base_id` + `composer_meta` columns. Per Q6 auto-upgrade: the section-seed mig (148) walks every existing draft and writes its `base_id` + section rows in-transaction.
|
||||
- Backend: `BaseService` (list, get-by-slug, get-default-for-code). `SubmissionDraftService.Create` seeds `base_id` + section rows. `SubmissionDraftService.Get` returns sections in `composer_meta.section_order`.
|
||||
- Frontend: sidebar picker "Vorlagenbasis" with the seeded bases. Section list rendered read-only above the existing preview (no edit yet) — every draft shows the section stack from mig-148 day one.
|
||||
- Render: unchanged — still uses v1 `resolveSubmissionTemplate` chain. `base_id` is informational this slice; v1 path stays compiled in as the safety net (gate: `base_id IS NULL OR no sections rows`).
|
||||
- **Ships:** lawyer sees the base concept land, no behavior change at export.
|
||||
|
||||
**Acceptance:**
|
||||
- Creating a new draft seeds `base_id` from firm + family default.
|
||||
- Sidebar picker renders ≥2 bases; selecting one PATCHes `base_id`.
|
||||
- Section list renders the base's default section set (read-only labels in active language).
|
||||
- All 11 existing drafts continue to render via v1 path (their `base_id` is NULL).
|
||||
- Unit tests cover: base default picker, section-list payload, migration round-trip.
|
||||
- All 11 existing drafts now carry sections rows + non-NULL `base_id` after mig 148; export still produces v1-equivalent output (sections rendered with their seeded Markdown via the seeded base, identical to v1's auto-Rubrum/letterhead/signature).
|
||||
- Unit tests cover: base default picker, section-list payload, mig-148 auto-seed idempotency, v1-equivalence after seed.
|
||||
|
||||
### Slice B — Editable prose sections + composer render pipeline
|
||||
|
||||
@@ -792,15 +851,15 @@ Each slice independently shippable. Coder picks up the next slice only after the
|
||||
### Slice C — Building blocks library + section picker
|
||||
|
||||
**Scope:**
|
||||
- Migrations: `submission_building_blocks` + `submission_building_block_versions` tables.
|
||||
- Backend: `BuildingBlockService` (CRUD + visibility filtering + version GC retention=20). Endpoints: `/api/blocks` (personal list), `/api/admin/submission-blocks` (admin CRUD), `/api/sections/{id}/insert-block` (the actual insert mechanic).
|
||||
- Frontend: `/admin/submission-blocks` editor (full-page list + edit + version log + restore, mirrors `/admin/email-templates` shape). Per-section `+ Baustein einfügen` modal with filter chips.
|
||||
- **Ships:** reusable building blocks.
|
||||
- Migrations: `submission_building_blocks` + `submission_building_block_admin_versions` tables.
|
||||
- Backend: `BuildingBlockService` (CRUD + visibility filtering + admin-side retention=20). Endpoints: `/api/blocks` (personal list, search), `/api/admin/submission-blocks` (admin CRUD), `/api/sections/{id}/insert-block` (the actual insert mechanic — copy text into section, no lineage stamp).
|
||||
- Frontend: `/admin/submission-blocks` editor (full-page list + edit + admin version log + restore, mirrors `/admin/email-templates` shape). Per-section `+ Baustein einfügen` modal with filter chips (section_key, proceeding_family, visibility tier reach).
|
||||
- **Ships:** reusable building blocks as paste sources.
|
||||
|
||||
**Acceptance:**
|
||||
- Admin creates a block at firm visibility → visible to every HLC user in the picker.
|
||||
- Lawyer inserts a block into a `requests` section → block content appended at cursor; `building_block_id` + version stamped.
|
||||
- Block update → existing drafts unaffected; "Aus Bibliothek aktualisieren" button surfaces on the section.
|
||||
- Lawyer inserts a block into a `requests` section → block content appended at cursor; section row has NO `building_block_id` reference (per Q2).
|
||||
- Library edits to the block do NOT propagate to in-flight drafts (by design — no link).
|
||||
- Visibility tiers enforced (private not visible to other users; team requires team membership; firm requires firm match; global open).
|
||||
|
||||
### Slice D — Rich-prose features (lists, numbered, blockquote, links, stylemap)
|
||||
@@ -829,13 +888,9 @@ Each slice independently shippable. Coder picks up the next slice only after the
|
||||
- Per-section "Nicht aufnehmen" toggle.
|
||||
- **Ships:** full lawyer control over composition.
|
||||
|
||||
### Slice G — Diff & merge for building-block refresh
|
||||
### Slice G — *Removed per Q2 / Q12 simplification*
|
||||
|
||||
**Scope:**
|
||||
- "Aus Bibliothek aktualisieren" button surfaces when section's snapshot_version < library_version.
|
||||
- Diff view (text-level, library-side library version vs section content).
|
||||
- Three-way: keep / overwrite / merge (side-by-side conflict markers).
|
||||
- **Ships:** lawyer can actively curate library updates per draft.
|
||||
The original Slice G ("diff & merge for building-block refresh") is dropped. m's ratification of Q2 dissolved the lineage machinery the slice depended on. There is no "refresh from library" affordance — see §8.4.
|
||||
|
||||
---
|
||||
|
||||
@@ -886,8 +941,8 @@ Issue text also mentioned `submission_drafts.audit_log` — this column doesn't
|
||||
7. **Performance with 100+ building blocks per firm.** Picker render time grows.
|
||||
- **Mitigation:** server-side filter + paginated list (limit 25 most-recent + free-text search input). Same shape as the party DB picker in t-paliad-287.
|
||||
|
||||
8. **Existing drafts orphaned.** If a lawyer leaves a v1 draft un-migrated and later edits it, the "upgrade" prompt might confuse them.
|
||||
- **Mitigation:** prompt is non-destructive (default NO, "Composer is opt-in for this draft"). v1 path keeps working indefinitely.
|
||||
8. **Auto-upgrade surprise (Q6).** The lawyer's next visit to a previously-edited draft shows the new Composer UI (section stack instead of the variable-only sidebar). Could be disorienting on first encounter.
|
||||
- **Mitigation:** existing `variables` jsonb overrides survive untouched; section content seeded with bag-driven Markdown so export produces v1-equivalent output until the lawyer edits a section. v1 fallback render path stays compiled in (gate: `base_id IS NULL OR no sections rows`) as a safety net. A one-time "Wir haben Ihre Entwürfe ins Composer-Format übernommen" Hinweis banner on first post-deploy visit explains the change.
|
||||
|
||||
9. **Section spec versioning.** If we tweak the default section set, in-flight drafts created before the change have a stale `composer_meta.section_order`.
|
||||
- **Mitigation:** `section_spec.version` field. Compose service uses the lawyer's `composer_meta.section_order` if set (always); else seeds from current base spec. Spec bumps only affect new drafts.
|
||||
@@ -895,6 +950,9 @@ Issue text also mentioned `submission_drafts.audit_log` — this column doesn't
|
||||
10. **Coupling between base and stylemap.** A base swap could fail if the new base doesn't declare every stylemap key the old base did.
|
||||
- **Mitigation:** required keys list enforced at base-upload-time validation. Specialist bases must declare paragraph + heading_1/2/3 + list_bullet + list_numbered + blockquote.
|
||||
|
||||
11. **Lawyer-typed content conflicts with variable bag (Q10).** With fully-editable caption/letterhead/signature sections, the lawyer might type a party name into the Rubrum that doesn't match `{{parties.claimant.0.name}}`. Variable substitution is a no-op on literal text — the exported `.docx` shows the typed name. If the project's party list later changes, the draft's Rubrum is stale.
|
||||
- **Mitigation:** the seed Markdown uses placeholders (`{{parties.claimant.0.name}}`), so an untouched section auto-tracks the bag. The lawyer's edit is opt-in: typing a literal name is an explicit choice. A small inline hint on the caption section ("Tipp: `{{parties.claimant.0.name}}` bleibt mit dem Projekt synchron") nudges placeholder usage. No automated detection of literal-vs-placeholder drift — out of scope.
|
||||
|
||||
---
|
||||
|
||||
## §15 Open follow-ups (NOT blocking m's go/no-go)
|
||||
@@ -937,27 +995,24 @@ NOT cronus (per project memory directive).
|
||||
## §17 Acceptance for the design (this doc)
|
||||
|
||||
1. ✅ All four headline requirements (sections, bases, editability, building blocks) have concrete data-model + UX + render-pipeline proposals.
|
||||
2. ✅ Every open design question (12 of them) has a recommended default + at least one alternative.
|
||||
3. ✅ Slice plan present (A → G), each independently shippable; Slice B is the smallest "Composer works" milestone.
|
||||
4. ✅ Migration path documented: existing 11 drafts continue via v1 path; opt-in upgrade per draft.
|
||||
5. ✅ Hard constraints honoured: `submission_drafts` shape preserved, `{{rule.X}}` aliases preserved, audit contract preserved, no new third-party Go dep.
|
||||
2. ✅ All 12 open design questions ratified by m on 2026-05-26 — see § m's decisions at the top of this doc. Body sections updated inline; §11 retains the historical recommendation matrix.
|
||||
3. ✅ Slice plan present (A → F, with G dropped per Q2 simplification). Slice B is the smallest "Composer works" milestone.
|
||||
4. ✅ Migration path documented: auto-upgrade all 11 existing drafts at mig-148 apply time per Q6. v1 fallback render path stays compiled in.
|
||||
5. ✅ Hard constraints honoured: `submission_drafts` shape preserved (additive columns only), `{{rule.X}}` aliases preserved, audit contract preserved, no new third-party Go dep.
|
||||
6. ✅ Premises verified live against the running paliad + DB; doc-vs-live conflicts flagged in §1 and §13.
|
||||
7. ✅ Risks identified with mitigations.
|
||||
8. ⏳ Inventor reports `DESIGN READY FOR REVIEW`. **Inventor MUST NOT shift to coder.** Head gates the inventor→coder transition.
|
||||
7. ✅ Risks identified with mitigations (11 risks, including Q6 + Q10 deviations).
|
||||
8. ✅ Inventor reports `ALL DESIGN QUESTIONS RATIFIED — design doc final, ready for Slice A coder shift`. **Inventor MUST NOT shift to coder.** Head gates the inventor→coder transition.
|
||||
|
||||
---
|
||||
|
||||
## §18 What m needs to ratify before coder shift
|
||||
## §18 Coder gate
|
||||
|
||||
Per task brief: no AskUserQuestion. Escalation via `mai instruct head` for the 12 §11 questions, batched. Default picks are listed inline; m can flip any to an alternative with one word.
|
||||
All 12 design questions ratified by m on 2026-05-26 via AskUserQuestion (paliadin-authorised override of the task brief's no-AskUserQuestion rule, per instruction msg #2391). The § m's decisions section at the top of this doc captures every pick; body sections + Slice plan updated inline.
|
||||
|
||||
Head's job after this doc lands:
|
||||
Head's job:
|
||||
|
||||
1. Surface §11's Q1–Q12 to m with the recommended defaults.
|
||||
2. Capture m's ratifications (or alternative picks) as an addendum at the top of this doc (§ m's decisions, 2026-MM-DD) — same shape as t-paliad-215's §2.
|
||||
3. Decide who codes Slice A: same worker as `/mai-coder` with this design + addendum as brief, fresh coder, or rescope.
|
||||
4. Park Slice B–G until Slice A ships + lawyer validation lands.
|
||||
|
||||
The decisions section gets committed on the same branch as this design (no separate branch).
|
||||
1. Decide who codes Slice A: same worker as `/mai-coder` with this design as brief, fresh coder, or rescope.
|
||||
2. Park Slice B–F until Slice A ships + lawyer validation lands.
|
||||
3. Slice G was removed per Q2 — don't spawn a successor for it.
|
||||
|
||||
Inventor parks here.
|
||||
|
||||
Reference in New Issue
Block a user