feat(submissions): t-paliad-370 S2 — editor draft-meta lifted into a header toolbar

docforge UX slice S2 (PRD docs/plans/prd-docforge-ux-2026-06-01.md §3).
De-densifies the draft editor (G1-d): draft management + template controls
move out of the variable sidebar into a horizontal, wrap-friendly header
strip above the working grid. The sidebar then holds only the fill-in work
— 'Aus Projekt importieren', parties, and the variable fields.

- submission-draft.tsx: new .submission-draft-toolbar strip carrying the
  switcher (+ '+ Neuer Entwurf'), name (+ Löschen), Stichwort (+ hint),
  Vorlagenbasis picker, language toggle, fallback notice + savestatus. The
  'Als .docx exportieren' button stays top-right in the header.
- New 👁 Vorschau button next to the base picker (id
  submission-draft-preview-base-btn), disabled 'Bald verfügbar' stub — S3
  wires it to the truthful base-preview modal (PRD §2).
- global.css: .submission-draft-toolbar* layout + .submission-draft-base-
  controls (select + 👁 on one row); single-column collapse < 900px.
- i18n: submissions.draft.base.preview(.soon) (DE/EN).

Pure relayout — NOT a behavior change. Every control keeps its id, so the
existing wiring is untouched (switcher, name save, keyword→composer_meta,
base picker PATCH base_id, language autosave, savestatus). Grounded in the
2-panel reality (§1.4): the conditional Abschnitte section panel is
unchanged (still display:none when a draft has no sections; S5 addresses
its discoverability). TS/CSS only, no backend.

bun build (i18n scan clean) + go vet ./... + go test ./... (15 ok, 0 fail).
This commit is contained in:
mAi
2026-06-01 18:29:01 +02:00
parent 3f20823e18
commit 064ba79ecb
4 changed files with 222 additions and 118 deletions

View File

@@ -1771,6 +1771,8 @@ const translations: Record<Lang, Record<string, string>> = {
// t-paliad-313 (m/paliad#141) Composer Slice A — base picker + section list.
"submissions.draft.base.label": "Vorlagenbasis",
"submissions.draft.base.hint": "Steuert Schriftarten, Briefkopf und Abschnitts-Defaults.",
"submissions.draft.base.preview": "Vorschau Vorlagenbasis",
"submissions.draft.base.preview.soon": "Bald verfügbar",
"submissions.draft.sections.title": "Abschnitte",
"submissions.draft.sections.hint": "Inhalt pro Abschnitt — Autosave nach 500 ms. Letztes Layout in Word.",
// t-paliad-349 (m/paliad#157) docforge slice 6 — template authoring page.
@@ -5124,6 +5126,8 @@ const translations: Record<Lang, Record<string, string>> = {
// t-paliad-313 (m/paliad#141) Composer Slice A — base picker + section list.
"submissions.draft.base.label": "Template base",
"submissions.draft.base.hint": "Drives fonts, letterhead, and section defaults.",
"submissions.draft.base.preview": "Preview template base",
"submissions.draft.base.preview.soon": "Coming soon",
"submissions.draft.sections.title": "Sections",
"submissions.draft.sections.hint": "Edit per section — autosaves after 500ms. Final layout in Word.",
// t-paliad-349 (m/paliad#157) docforge slice 6 — template authoring page.

View File

@@ -2860,6 +2860,8 @@ export type I18nKey =
| "submissions.draft.back"
| "submissions.draft.base.hint"
| "submissions.draft.base.label"
| "submissions.draft.base.preview"
| "submissions.draft.base.preview.soon"
| "submissions.draft.import.button"
| "submissions.draft.keyword.hint"
| "submissions.draft.keyword.label"

View File

@@ -6218,6 +6218,85 @@ dialog.modal::backdrop {
flex: 0 0 auto;
}
/* t-paliad-370 S2 — meta toolbar strip. Draft management + template
controls are lifted out of the sidebar into a horizontal, wrap-friendly
header strip above the working grid; the sidebar then holds only the
fill-in work (parties + variables). */
.submission-draft-toolbar {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 0.75rem 1.25rem;
margin-bottom: 1.5rem;
padding: 0.9rem 1rem;
border: 1px solid var(--color-border);
border-radius: 8px;
background: var(--color-surface, #fff);
}
/* Zero the stacked sidebar margins so the moved blocks align on the
toolbar baseline, and size them as flexible cells. */
.submission-draft-toolbar > .submission-draft-switcher,
.submission-draft-toolbar > .submission-draft-name-row,
.submission-draft-toolbar > .submission-draft-keyword-row,
.submission-draft-toolbar > .submission-draft-base-row,
.submission-draft-toolbar > .submission-draft-language-row {
margin: 0;
}
.submission-draft-toolbar > .submission-draft-switcher { flex: 1 1 240px; }
.submission-draft-toolbar > .submission-draft-name-row { flex: 1 1 200px; }
.submission-draft-toolbar > .submission-draft-keyword-row {
flex: 1 1 200px;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.submission-draft-toolbar > .submission-draft-base-row { flex: 1 1 240px; }
.submission-draft-toolbar > .submission-draft-language-row { flex: 0 0 auto; }
/* savestatus + fallback notice break onto their own full-width line at the
bottom of the toolbar. */
.submission-draft-toolbar > .submission-draft-savestatus,
.submission-draft-toolbar > .submission-draft-language-fallback {
flex-basis: 100%;
margin: 0;
}
/* Base picker + 👁 Vorschau button share one row (disabled stub until S3). */
.submission-draft-base-controls {
display: flex;
gap: 0.4rem;
align-items: center;
}
.submission-draft-base-controls select {
flex: 1 1 auto;
}
.submission-draft-preview-base-btn {
flex: 0 0 auto;
width: 34px;
height: 34px;
padding: 0;
font-size: 1rem;
line-height: 1;
}
.submission-draft-preview-base-btn:disabled {
opacity: 0.5;
cursor: default;
}
@media (max-width: 900px) {
.submission-draft-toolbar > .submission-draft-switcher,
.submission-draft-toolbar > .submission-draft-name-row,
.submission-draft-toolbar > .submission-draft-keyword-row,
.submission-draft-toolbar > .submission-draft-base-row {
flex-basis: 100%;
}
}
.submission-draft-grid {
display: grid;
grid-template-columns: minmax(280px, 360px) 1fr;

View File

@@ -75,132 +75,151 @@ export function renderSubmissionDraft(): string {
</div>
</header>
<div className="submission-draft-grid">
{/* Sidebar — draft switcher + variable groups. */}
<aside className="submission-draft-sidebar" id="submission-draft-sidebar">
<div className="submission-draft-switcher">
<label htmlFor="submission-draft-pick" data-i18n="submissions.draft.switcher.label">
Entwurf
</label>
<select id="submission-draft-pick" />
<button
type="button"
id="submission-draft-new-btn"
className="btn-small btn-secondary"
data-i18n="submissions.draft.action.new">
+ Neuer Entwurf
</button>
</div>
{/* t-paliad-370 S2 — meta toolbar. Draft management +
template controls lifted out of the sidebar into a header
strip so the sidebar holds only the fill-in work (parties
+ variables). Pure relayout: every control keeps its id +
wiring (switcher, name, keyword→composer_meta, base picker
PATCH base_id, language autosave). */}
<div className="submission-draft-toolbar" id="submission-draft-toolbar">
<div className="submission-draft-switcher">
<label htmlFor="submission-draft-pick" data-i18n="submissions.draft.switcher.label">
Entwurf
</label>
<select id="submission-draft-pick" />
<button
type="button"
id="submission-draft-new-btn"
className="btn-small btn-secondary"
data-i18n="submissions.draft.action.new">
+ Neuer Entwurf
</button>
</div>
<div className="submission-draft-name-row">
<input
type="text"
id="submission-draft-name"
className="entity-form-input"
data-i18n-placeholder="submissions.draft.name.placeholder"
placeholder="Name dieses Entwurfs"
/>
<button
type="button"
id="submission-draft-delete-btn"
className="btn-small btn-link-danger"
data-i18n="submissions.draft.action.delete">
L&ouml;schen
</button>
</div>
<div className="submission-draft-name-row">
<input
type="text"
id="submission-draft-name"
className="entity-form-input"
data-i18n-placeholder="submissions.draft.name.placeholder"
placeholder="Name dieses Entwurfs"
/>
<button
type="button"
id="submission-draft-delete-btn"
className="btn-small btn-link-danger"
data-i18n="submissions.draft.action.delete">
L&ouml;schen
</button>
</div>
{/* t-paliad-354 — keyword that leads the exported
document name "<date> <keyword> (<case>)". Empty
falls back to the auto-derived rule name; the
placeholder shows that default. Persisted to
composer_meta.filename_keyword via the draft-save
path on change. Grouped with the draft-name row
(naming controls) ahead of the template controls
(base + language) per t-paliad-359. */}
<div className="submission-draft-keyword-row">
<label
htmlFor="submission-draft-keyword"
data-i18n="submissions.draft.keyword.label">
Stichwort (Dateiname)
</label>
<input
type="text"
id="submission-draft-keyword"
className="entity-form-input"
data-i18n-placeholder="submissions.draft.keyword.placeholder"
placeholder="Automatisch aus dem Schriftsatztyp"
/>
<p
className="submission-draft-keyword-hint"
id="submission-draft-keyword-hint"
data-i18n="submissions.draft.keyword.hint">
Führt den Dateinamen an: &lt;Datum&gt; &lt;Stichwort&gt; (&lt;Aktenzeichen&gt;).
</p>
</div>
{/* t-paliad-354 — keyword that leads the exported document
name "<date> <keyword> (<case>)". Empty falls back to the
auto-derived rule name. Persisted to
composer_meta.filename_keyword on change. */}
<div className="submission-draft-keyword-row">
<label
htmlFor="submission-draft-keyword"
data-i18n="submissions.draft.keyword.label">
Stichwort (Dateiname)
</label>
<input
type="text"
id="submission-draft-keyword"
className="entity-form-input"
data-i18n-placeholder="submissions.draft.keyword.placeholder"
placeholder="Automatisch aus dem Schriftsatztyp"
/>
<p
className="submission-draft-keyword-hint"
id="submission-draft-keyword-hint"
data-i18n="submissions.draft.keyword.hint">
Führt den Dateinamen an: &lt;Datum&gt; &lt;Stichwort&gt; (&lt;Aktenzeichen&gt;).
</p>
</div>
{/* t-paliad-313 (m/paliad#141) Composer Slice A —
base picker. Hydrated by client/submission-draft.ts
once /api/submission-bases returns. Disabled
for pre-Composer drafts (base_id NULL); switching
autosaves the draft. */}
<div
className="submission-draft-base-row"
id="submission-draft-base-row"
style="display:none">
<label htmlFor="submission-draft-base" data-i18n="submissions.draft.base.label">
Vorlagenbasis
</label>
{/* t-paliad-313 (m/paliad#141) Composer Slice A — base
picker. Hydrated by client/submission-draft.ts once
/api/submission-bases returns; switching autosaves
base_id. t-paliad-370 S2 adds the 👁 Vorschau button
(disabled stub; S3 wires it to the truthful base-preview
modal, PRD §2). */}
<div
className="submission-draft-base-row"
id="submission-draft-base-row"
style="display:none">
<label htmlFor="submission-draft-base" data-i18n="submissions.draft.base.label">
Vorlagenbasis
</label>
<div className="submission-draft-base-controls">
<select id="submission-draft-base" />
<p
className="submission-draft-base-hint"
id="submission-draft-base-hint"
data-i18n="submissions.draft.base.hint">
Steuert Schriftarten, Briefkopf und Abschnitts-Defaults.
</p>
</div>
{/* t-paliad-276 — output language toggle (DE/EN).
Hydrated by client/submission-draft.ts; switching
autosaves the draft and re-renders the preview. */}
<div
className="submission-draft-language-row"
id="submission-draft-language-row"
role="radiogroup"
aria-labelledby="submission-draft-language-label">
<span
id="submission-draft-language-label"
className="submission-draft-language-label"
data-i18n="submissions.draft.language">
Sprache
</span>
<label className="submission-draft-language-option">
<input
type="radio"
name="submission-draft-language"
value="de"
id="submission-draft-language-de"
/>
<span data-i18n="submissions.draft.language.de">DE</span>
</label>
<label className="submission-draft-language-option">
<input
type="radio"
name="submission-draft-language"
value="en"
id="submission-draft-language-en"
/>
<span data-i18n="submissions.draft.language.en">EN</span>
</label>
<button
type="button"
id="submission-draft-preview-base-btn"
className="btn-icon submission-draft-preview-base-btn"
disabled
aria-label="Vorschau Vorlagenbasis"
data-i18n-title="submissions.draft.base.preview.soon"
title="Bald verf&uuml;gbar">
&#128065;
</button>
</div>
<p
className="submission-draft-language-fallback"
id="submission-draft-language-fallback"
style="display:none"
data-i18n="submissions.draft.language.fallback_notice">
Fallback: universelles Skelett (keine sprachspezifische Vorlage).
className="submission-draft-base-hint"
id="submission-draft-base-hint"
data-i18n="submissions.draft.base.hint">
Steuert Schriftarten, Briefkopf und Abschnitts-Defaults.
</p>
</div>
<p className="submission-draft-savestatus" id="submission-draft-savestatus" />
{/* t-paliad-276 — output language toggle (DE/EN). Hydrated
by client/submission-draft.ts; switching autosaves the
draft and re-renders the preview. */}
<div
className="submission-draft-language-row"
id="submission-draft-language-row"
role="radiogroup"
aria-labelledby="submission-draft-language-label">
<span
id="submission-draft-language-label"
className="submission-draft-language-label"
data-i18n="submissions.draft.language">
Sprache
</span>
<label className="submission-draft-language-option">
<input
type="radio"
name="submission-draft-language"
value="de"
id="submission-draft-language-de"
/>
<span data-i18n="submissions.draft.language.de">DE</span>
</label>
<label className="submission-draft-language-option">
<input
type="radio"
name="submission-draft-language"
value="en"
id="submission-draft-language-en"
/>
<span data-i18n="submissions.draft.language.en">EN</span>
</label>
</div>
<p
className="submission-draft-language-fallback"
id="submission-draft-language-fallback"
style="display:none"
data-i18n="submissions.draft.language.fallback_notice">
Fallback: universelles Skelett (keine sprachspezifische Vorlage).
</p>
<p className="submission-draft-savestatus" id="submission-draft-savestatus" />
</div>
<div className="submission-draft-grid">
{/* Sidebar — parties + variable groups (the fill-in work).
Draft/template meta lives in the toolbar above. */}
<aside className="submission-draft-sidebar" id="submission-draft-sidebar">
{/* t-paliad-277: "Aus Projekt importieren" + last-
imported-at timestamp. Only visible when the