Some checks failed
CI / Unit & Component Tests (push) Failing after 2m1s
CI / OCR Service Tests (push) Successful in 28s
CI / Backend Unit Tests (push) Successful in 6m21s
CI / fail2ban Regex (push) Successful in 44s
CI / Semgrep Security Scan (push) Successful in 27s
CI / Compose Bucket Idempotency (push) Successful in 1m8s
nightly / deploy-staging (push) Successful in 4m43s
nightly / npm-audit (push) Failing after 19s
Renovate / renovate (push) Failing after 38s
Adds the Mappe design handoff as in-repo ground truth and closes out the design-token foundation: --radius-sm/md/full + --shadow-sm/md tokens (distinct dark values in both dark blocks) and the canonical 10-color $lib/shared/avatarPalette.ts (AA-darkened sage/amber/sand swatches), guarded by avatarPalette.spec.ts. Closes #854.
218 lines
12 KiB
Markdown
218 lines
12 KiB
Markdown
# EPIC: Familienarchiv Visual Redesign ("Mappe")
|
||
|
||
> Implement **in order**. Stories 1–4 are foundation and shared primitives — every screen
|
||
> depends on them. Do not start a screen story until 1–4 are merged, or the header, avatar,
|
||
> and tokens get re-implemented per screen and drift. Read `DESIGN_RULES.md` first; open the
|
||
> matching `prototypes/*.dc.html` for pixel ground truth while building each story.
|
||
|
||
## Epic goal
|
||
|
||
Reskin the entire Familienarchiv app to the unified "Mappe" archival direction and ship
|
||
three new sections (Geschichten, Zeitstrahl, Aktivitäten). All copy German-first via
|
||
Paraglide; light + dark mode; De Gruyter icon convention preserved.
|
||
|
||
## Epic-level acceptance criteria
|
||
|
||
- [ ] All eight screens match their prototype in light **and** dark mode.
|
||
- [ ] Header, page-header, avatar, segmented control, and card exist as **single shared
|
||
components** — zero duplication of the header markup or the avatar-color function.
|
||
- [ ] No raw hex in components; everything references the semantic tokens.
|
||
- [ ] Every visible string is a Paraglide message key, German authored first; `en`/`es`
|
||
stubs added.
|
||
- [ ] Icons rendered as `<img>`, invert correctly in dark mode.
|
||
- [ ] No gradients, blur, emoji, or transform-based motion introduced.
|
||
|
||
---
|
||
|
||
> **Reframe (2026-06-16): this is alignment, not greenfield.** An audit of the live codebase
|
||
> found the substrate already in place — the full token system (`DESIGN_RULES §1–2`), dark
|
||
> mode, the app header, and the three "new" sections (Geschichten, Zeitstrahl, Aktivitäten)
|
||
> all already exist. So **Stories 1–4 below are close-out + extraction**, not from-scratch
|
||
> builds, and Stories 5–11 are "align the existing screen to its prototype," not new pages.
|
||
> The detailed, trackable breakdown lives in the Gitea milestone **"Mappe Visual Redesign"**
|
||
> (issues split into *shared components* then *pages*); **Phase B** at the bottom of this file
|
||
> lists the screens that previously had no prototype and now do.
|
||
|
||
## Story 1 — Foundation: tokens, fonts, theme
|
||
|
||
**Goal:** establish the visual substrate the whole app reads from.
|
||
|
||
- Port `prototypes/colors_and_type.css` into the app's token layer (Tailwind 4 `@theme` /
|
||
`layout.css`). Keep every variable name in `DESIGN_RULES.md §1`.
|
||
- Wire Montserrat + Tinos (or licensed Gotham/Times) and the `--font-sans`/`--font-serif` vars.
|
||
- Implement light/dark via `:root[data-theme='dark']` + the pre-paint boot script that reads
|
||
`localStorage['theme']`. Add the global `img[src*='degruyter-icons']{filter:invert(1)}`
|
||
dark rule.
|
||
|
||
**Done when:** a throwaway page using `var(--c-*)` tokens renders correct in both themes; no
|
||
flash of wrong theme on reload.
|
||
|
||
## Story 2 — Shared: app header + nav (`ArchiveHeader`)
|
||
|
||
**Spec:** `DESIGN_RULES.md §4`. Prototype: `ArchiveHeader.dc.html`.
|
||
|
||
- One sticky header component: mint stripe, wordmark, nav with active-key prop, theme
|
||
toggle, user chip. Nav routes to all sections.
|
||
- Theme toggle drives the Story 1 mechanism.
|
||
|
||
**Done when:** header renders identically on every route; active item shows the mint
|
||
underline; toggle flips theme and persists.
|
||
|
||
## Story 3 — Shared: avatar + deterministic color
|
||
|
||
**Spec:** `DESIGN_RULES.md §5`.
|
||
|
||
- `avatarFor(name)` util (hash → palette index + initials) + an `<Avatar name size>`
|
||
component supporting 26/28/40/48px and the overlapping-stack ring variant.
|
||
|
||
**Done when:** the same name yields the same color everywhere; stacks overlap with the
|
||
surface-colored ring.
|
||
|
||
## Story 4 — Shared: page-header, segmented control, card, metadata, empty state
|
||
|
||
**Spec:** `DESIGN_RULES.md §3, §6, §7`.
|
||
|
||
- `PageHeader` (eyebrow + 4px mint left rule + serif h1 + italic lede + right slot).
|
||
- `SegmentedControl` (active = navy). `Card` (mint top/left border variants). `MetaLine`
|
||
(` · ` separated, optional leading icon). `EmptyState` (dashed, serif + ellipsis).
|
||
|
||
**Done when:** each primitive matches the prototype and is consumed by the screen stories
|
||
below — not re-styled inline.
|
||
|
||
---
|
||
|
||
## Story 5 — Dokumente (dashboard / search results)
|
||
|
||
**Route:** `/`. **Prototype:** `Dokumente.dc.html`.
|
||
|
||
PageHeader (eyebrow "Archiv", title "Dokumente", lede, right count "147 Dokumente · 38
|
||
Personen"). Then: a **search bar card** (input `Titel, Personen, Tags durchsuchen…` +
|
||
"Datum ↓" sort + "Filter" buttons); a **resume strip** (mint left border, "Weiter bei:" +
|
||
italic underlined doc link); a **`1fr 320px` grid** — left: "Zuletzt hinzugefügt" list
|
||
(title · avatar stack · right-aligned date `width:128px` · status dot+label `width:118px`),
|
||
right column: **upload dropzone** (dashed, Upload icon, `PDF, JPG, PNG, TIFF bis 50 MB`) +
|
||
"Benötigt Metadaten" card; below full-width **Mission Control** — 3 tiles (Segmentierung /
|
||
Transkription / Zur Überprüfung), each with a caption, a pill "skill" hint, a weekly count,
|
||
and a list of linked items. Right column collapses below `lg`; main goes full-width.
|
||
|
||
**Done when:** matches prototype both themes; status dots use the §7 colors; grid collapses.
|
||
|
||
## Story 6 — Personen (directory)
|
||
|
||
**Route:** `/persons`. **Prototype:** `Personen.dc.html`.
|
||
|
||
PageHeader (eyebrow "Verzeichnis") + right count "38 Personen". Search input
|
||
(`z.B. Oma Frieda, Onkel Karl…`) + segmented control (Alle / Personen / Institutionen /
|
||
Gruppen). **3-column card grid**; each card: 48px avatar, serif name, relation sub, optional
|
||
type **badge** (Institution/Gruppe/Unbekannt — §1 colors), divider, meta line
|
||
`✉ N Briefe · N Dokumente`. Cards carry the 3px mint top border.
|
||
|
||
**Done when:** badge colors correct; avatar colors deterministic; grid responsive.
|
||
|
||
## Story 7 — Briefwechsel — DROPPED
|
||
|
||
The two-person letter-exchange feature was removed from the product. Its prototype, route, and
|
||
nav entry no longer exist. Skip.
|
||
|
||
## Story 8 — Geschichten (story collections list) — NEW
|
||
|
||
**Route:** `/geschichten`. **Prototype:** `Geschichten.dc.html`.
|
||
|
||
PageHeader (eyebrow "Sammlungen") + primary button "Neue Geschichte". Segmented control
|
||
(Alle / Veröffentlicht / In Arbeit / Entwurf) + Filter button. **2-column card grid**; each
|
||
card (link, mint top border): tag chips (dot + UPPERCASE label), serif 24px title, serif dek,
|
||
meta line `📅 range · N Dokumente · N Personen`, footer with overlapping avatar stack + status
|
||
dot/label. Cards link to the story detail.
|
||
|
||
**Done when:** tag dots and status colors correct; cards link to Story 9.
|
||
|
||
## Story 9 — Geschichte (single story detail) — NEW
|
||
|
||
**Route:** `/geschichten/:id`. **Prototype:** `Geschichte.dc.html`. **Two variants**
|
||
(`variant` prop): **"Lesereise"** (a guided reading — intro + narration blocks + letter
|
||
cards + annotation notes) and **"Sammlung"** (a collection — intro + "Erwähnte Dokumente"
|
||
list). Centered `max-width:880px` article card (mint top border, `padding:48px 56px`):
|
||
type badge, 38px serif title, byline row (author avatar + name + "zusammengestellt am …" +
|
||
Bearbeiten / Löschen actions), intro paragraph, then ordered **blocks**:
|
||
|
||
- **narration** — 3px mint left rule, serif italic 18px.
|
||
- **letter** — clickable row: 40px tile w/ Mail icon, serif title, meta `date · von X an Y`,
|
||
trailing Arrow-Right icon.
|
||
- **note** — mint-tinted (`--c-accent-bg`) left-rule box, "Anmerkung" caption + serif italic.
|
||
|
||
**Done when:** both variants render from the prop; block types styled per spec; Löschen uses
|
||
`--c-danger`.
|
||
|
||
## Story 10 — Zeitstrahl (timeline) — NEW
|
||
|
||
**Route:** `/zeitstrahl`. **Prototype:** `Zeitstrahl.dc.html`.
|
||
|
||
PageHeader (eyebrow "Chronik") + right count. Segmented control (Alle / Briefe / Personen /
|
||
Ereignisse) + a small legend. **Centered vertical spine** (`max-width:760px`, 2px mint center
|
||
line). Item types stacked on the spine: **year** pill (navy), **summary** card (count + a
|
||
12-bar monthly-density mini chart in mint + range labels), **letter** cards **alternating
|
||
left/right** with a spine dot (`2px solid --c-primary`) and optional tag pill, **person**
|
||
node (28px navy circle glyph + name + derived meta), **curated** node (★, mint left rule),
|
||
**historical** band (full-width, Globe icon, serif italic, top/bottom hairline).
|
||
|
||
**Done when:** spine centered; letters alternate; bars scale to value %; all five item types
|
||
render.
|
||
|
||
## Story 11 — Aktivitäten (activity feed) — NEW
|
||
|
||
**Route:** `/aktivitaeten`. **Prototype:** `Aktivitaeten.dc.html`.
|
||
|
||
PageHeader (eyebrow "Verlauf") + "Aktualisieren" button. Segmented control (Alle /
|
||
Transkription / Uploads / Personen). Feed grouped by day (Heute / Gestern / Diese Woche),
|
||
each group a UPPERCASE caption + rows. Each **row**: 40px avatar with a small **action-icon
|
||
badge** bottom-right (Check/Upload/Chat/Edit/…), then a sentence — bold actor name +
|
||
Montserrat verb + *italic underlined* target link — and a time sub.
|
||
|
||
**Done when:** grouping + icon badges match; target links styled with mint underline.
|
||
|
||
## Story 12 (optional) — Regeln (internal style reference)
|
||
|
||
**Prototype:** `Regeln.dc.html`. Internal page documenting the seven blocks (Typografie,
|
||
Farbe, Seitenkopf, Steuerung, Avatare, Metadaten/Leerzustände). Build only if the team wants
|
||
a living in-app reference; otherwise `DESIGN_RULES.md` is the canonical record. Gate behind
|
||
admin/dev.
|
||
|
||
---
|
||
|
||
## Suggested order & dependencies
|
||
|
||
```
|
||
1 Tokens ─┬─ 2 Header ──┐
|
||
├─ 3 Avatar ──┼─→ 5 Dokumente, 6 Personen,
|
||
└─ 4 Primitives┘ 8 Geschichten → 9 Geschichte,
|
||
10 Zeitstrahl, 11 Aktivitäten (parallelizable)
|
||
12 Regeln (optional, last)
|
||
→ then Phase B (added screens), all depend on 1–4
|
||
```
|
||
|
||
---
|
||
|
||
## Phase B — Added screens (previously un-prototyped pages)
|
||
|
||
These are the pages the original handoff never covered. Each now has a hifi `.dc.html`
|
||
prototype in `prototypes/`. All depend on the shared primitives from Stories 1–4 and are
|
||
parallelizable among themselves. **Each maps to one Gitea page-issue** in the milestone.
|
||
Admin + OCR pages are explicitly **out of scope** here (phase-2 milestone).
|
||
|
||
| # | Screen | Prototype | Route(s) | Done when |
|
||
|---|---|---|---|---|
|
||
| B1 | Dokumente-Liste | `Dokumente-Liste.dc.html` | `/documents` | PageHeader; search card; AND/OR segmented; grouped mint-top cards; avatar-stack rows; 7px status dot+label; pagination |
|
||
| B2 | Dokument-Detail | `Dokument-Detail.dc.html` | `/documents/[id]` | compact top bar w/ mint accent bar; PDF pane + transcription panel w/ Lesen/Bearbeiten segmented + turquoise mode; details card |
|
||
| B3 | Dokument-Bearbeiten | `Dokument-Bearbeiten.dc.html` | `/documents/[id]/edit`, `/new`, `/bulk-edit` | split pane; progress strip; Wer&Wann + Beschreibung cards; dropzone (new); action bar (Löschen danger / Abbrechen / Zur Überprüfung / Speichern) |
|
||
| B4 | PersonDetail | `PersonDetail.dc.html` | `/persons/[id]` | PageHeader; 2-col mint-top cards; deterministic avatar; correspondents + relationships + letter lists |
|
||
| B5 | PersonForm | `PersonForm.dc.html` | `/persons/new`, `/[id]/edit` | PageHeader; Stammdaten card w/ type segmented; caps labels; Namensverlauf; edit-only merge danger zone; save bar |
|
||
| B6 | PersonReview | `PersonReview.dc.html` | `/persons/review` | PageHeader + count; row card w/ muted avatar; idle/rename/merge states; danger merge+delete; confirm dialog; empty state |
|
||
| B7 | Geschichte-Editor | `Geschichte-Editor.dc.html` | `/geschichten/new`, `/[id]/edit` | type-pick segmented; prose editor toolbar; journey editor w/ **color-only** drag; sidebar status+persons; save bar |
|
||
| B8 | Ereignis-Editor | `Ereignis-Editor.dc.html` | `/zeitstrahl/events/new`, `/[id]/edit` | PageHeader; Wann&Was card w/ type segmented + date precision + danger error; persons/docs sidebar; save bar |
|
||
| B9 | Stammbaum | `Stammbaum.dc.html` | `/stammbaum` | PageHeader + count; node cards w/ **§5 avatar** in resting/selected/dimmed; line connectors; side panel; zoom controls; empty state |
|
||
| B10 | Themen | `Themen.dc.html` | `/themen` | PageHeader + count; segmented filter; mint-top cards w/ **§1 tag-dot** (not stripe); child rows; empty state |
|
||
| B11 | Anreicherung | `Anreicherung.dc.html` | `/enrich`, `/[id]`, `/done` | list (status rows) / step (progress bar + split pane + action bar) / done (success card) |
|
||
| B12 | Profil | `Profil.dc.html` | `/profile`, `/users/[id]` | PageHeader; 2-col data/password cards; token banners; notifications; public profile card w/ avatar |
|
||
| B13 | Anmeldung | `Anmeldung.dc.html` | `/login`, `/register`, `/forgot-password`, `/reset-password` | self-contained branded shell (no app header); Tinos sentence-case titles; 17px inputs; token banners |
|
||
| B14 | Hilfe-Transkription | `Hilfe-Transkription.dc.html` | `/hilfe/transkription` | PageHeader; article column; rule cards w/ De Gruyter icons (no emoji); fixes `border-brand-sand`/`bg-white` bugs |
|