feat: Expandable metadata header with labeled "Details" toggle #175

Closed
opened 2026-04-05 09:28:59 +02:00 by marcel · 7 comments
Owner

Summary

Replace the static document metadata display with an expandable drawer triggered by a labeled "Details ▼" toggle button in the topbar. The drawer pushes content down (not overlay) and shows a 3-column grid (desktop) or single-column stack (mobile) with document details, person cards, and tag chips.

This replaces the removed bottom panel — metadata is now exclusively accessed via this topbar drawer in all modes (read, transcribe, annotate).

Motivation

  • The previous bottom panel was inconsistent across modes and wasted vertical space
  • A bare chevron icon is not discoverable enough for our 60+ year old target users
  • The labeled "Details ▼" toggle with a 44×28px minimum tap target ensures accessibility

Spec

📄 docs/specs/expandable-metadata-header-spec.html — open locally in browser for full mockups

Key screens

Screen Description
S1 Desktop — collapsed (default)
S2 Desktop — expanded drawer with 3-column grid
S3 Mobile — collapsed
S4 Mobile — expanded (single-column stack)
S5 Non-transcribe mode (same pattern, no bottom panel)

Design decisions

  • Labeled toggle ("Details ▼" / "Details ▲") — not a bare chevron. Min tap target 44×28px
  • Push layout — drawer pushes content down, not overlay. No z-index stacking issues
  • 3-column grid (desktop): Details | Personen | Schlagwörter. Single-column on mobile
  • Person cards with avatar circle, full name as link to person detail page
  • Tag chips — read-only display, click navigates to filtered search
  • Svelte slide transition for smooth open/close animation
  • No bottom panel anywhere — this drawer is the single metadata access pattern

Component changes

Component Action
DocumentTopBar.svelte Add "Details" toggle button and drawer markup
MetadataDrawer.svelte New — 3-column grid with details, persons, tags
DocumentBottomPanel.svelte Remove entirely

i18n keys needed

  • details_toggle_label — "Details"
  • details_section_details — "Details"
  • details_section_persons — "Personen"
  • details_section_tags — "Schlagwörter"
  • details_field_date — "Datum"
  • details_field_sender — "Absender"
  • details_field_receivers — "Empfänger"
  • details_field_status — "Status"

Acceptance criteria

  • "Details ▼" toggle visible in topbar with min 44×28px tap target
  • Click opens drawer below topbar, pushing content down (Svelte slide transition)
  • Drawer shows 3-column grid on desktop (≥768px), single-column on mobile
  • Person names link to /persons/{id}
  • Tag chips are displayed (read-only)
  • Toggle label changes to "Details ▲" when open
  • Drawer state is local (not persisted across page loads)
  • DocumentBottomPanel.svelte is removed from the codebase
  • Works in all modes: read, transcribe, annotate
  • All i18n keys added for de/en/es
## Summary Replace the static document metadata display with an **expandable drawer** triggered by a labeled "Details ▼" toggle button in the topbar. The drawer pushes content down (not overlay) and shows a 3-column grid (desktop) or single-column stack (mobile) with document details, person cards, and tag chips. This replaces the removed bottom panel — metadata is now exclusively accessed via this topbar drawer in **all modes** (read, transcribe, annotate). ## Motivation - The previous bottom panel was inconsistent across modes and wasted vertical space - A bare chevron icon is not discoverable enough for our 60+ year old target users - The labeled "Details ▼" toggle with a 44×28px minimum tap target ensures accessibility ## Spec 📄 **[`docs/specs/expandable-metadata-header-spec.html`](../blob/main/docs/specs/expandable-metadata-header-spec.html)** — open locally in browser for full mockups ### Key screens | Screen | Description | |--------|-------------| | S1 | Desktop — collapsed (default) | | S2 | Desktop — expanded drawer with 3-column grid | | S3 | Mobile — collapsed | | S4 | Mobile — expanded (single-column stack) | | S5 | Non-transcribe mode (same pattern, no bottom panel) | ### Design decisions - **Labeled toggle** ("Details ▼" / "Details ▲") — not a bare chevron. Min tap target 44×28px - **Push layout** — drawer pushes content down, not overlay. No z-index stacking issues - **3-column grid** (desktop): Details | Personen | Schlagwörter. Single-column on mobile - **Person cards** with avatar circle, full name as link to person detail page - **Tag chips** — read-only display, click navigates to filtered search - **Svelte slide transition** for smooth open/close animation - **No bottom panel anywhere** — this drawer is the single metadata access pattern ### Component changes | Component | Action | |-----------|--------| | `DocumentTopBar.svelte` | Add "Details" toggle button and drawer markup | | `MetadataDrawer.svelte` | **New** — 3-column grid with details, persons, tags | | `DocumentBottomPanel.svelte` | **Remove entirely** | ### i18n keys needed - `details_toggle_label` — "Details" - `details_section_details` — "Details" - `details_section_persons` — "Personen" - `details_section_tags` — "Schlagwörter" - `details_field_date` — "Datum" - `details_field_sender` — "Absender" - `details_field_receivers` — "Empfänger" - `details_field_status` — "Status" ## Acceptance criteria - [ ] "Details ▼" toggle visible in topbar with min 44×28px tap target - [ ] Click opens drawer below topbar, pushing content down (Svelte slide transition) - [ ] Drawer shows 3-column grid on desktop (≥768px), single-column on mobile - [ ] Person names link to `/persons/{id}` - [ ] Tag chips are displayed (read-only) - [ ] Toggle label changes to "Details ▲" when open - [ ] Drawer state is local (not persisted across page loads) - [ ] `DocumentBottomPanel.svelte` is removed from the codebase - [ ] Works in all modes: read, transcribe, annotate - [ ] All i18n keys added for de/en/es
marcel added the featureui labels 2026-04-05 09:30:07 +02:00
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Questions & Observations

  • Component decomposition looks rightMetadataDrawer.svelte as a new component, Details toggle in DocumentTopBar.svelte. But the topbar is already ~150 lines after the recent person chip work. Adding the toggle button + drawer markup could push it past the splitting threshold. Should the toggle button + drawer be extracted as a DetailsToggle.svelte that owns the open/close state and renders the drawer via a slot or child component?

  • Props explosion on DocumentTopBar — the topbar already takes doc, canWrite, canAnnotate, fileUrl, annotateMode. The drawer needs the full document (date, sender, receivers, tags, status). That's already in doc, but are tags loaded in the current Doc type? The existing type definition has no tags field. The +page.server.ts load function will need to supply tags — is that a new API call or does the existing document endpoint already include them?

  • DocumentBottomPanel.svelte removal — the issue says "remove entirely." Before deleting, we need to verify: does any route besides documents/[id] use it? A quick grep for the import will confirm. If it's only used in one place, clean deletion. If it's referenced elsewhere, those references need updating too.

  • Svelte slide transition — the spec calls for a Svelte slide transition. This is transition:slide from svelte/transition. Works well, but it requires the drawer to be conditionally rendered with {#if}, not just display: none. That's the correct approach anyway — no DOM when collapsed means no accessibility tree pollution.

Suggestions

  • TDD approach: Start with a Vitest test for MetadataDrawer.svelte — render it with a mock document that has sender, receivers, and tags. Assert that person names render as links to /persons/{id}. Then test the toggle: render DetailsToggle, click it, assert the drawer appears. These are the two core behaviors.

  • Keep the drawer data-freeMetadataDrawer should receive pre-formatted props (date: string, sender: Person, receivers: Person[], tags: Tag[]), not the raw doc object. This keeps the component pure and testable without needing to construct a full document object in tests.

  • i18n key naming — the proposed keys like details_field_date follow the pattern, but consider doc_details_field_date to namespace them under the document detail page and avoid collision with any future "details" sections in other contexts.</n">

## 👨‍💻 Felix Brandt — Senior Fullstack Developer ### Questions & Observations - **Component decomposition looks right** — `MetadataDrawer.svelte` as a new component, Details toggle in `DocumentTopBar.svelte`. But the topbar is already ~150 lines after the recent person chip work. Adding the toggle button + drawer markup could push it past the splitting threshold. Should the toggle button + drawer be extracted as a `DetailsToggle.svelte` that owns the open/close state and renders the drawer via a slot or child component? - **Props explosion on DocumentTopBar** — the topbar already takes `doc`, `canWrite`, `canAnnotate`, `fileUrl`, `annotateMode`. The drawer needs the full document (date, sender, receivers, tags, status). That's already in `doc`, but are tags loaded in the current `Doc` type? The existing type definition has no `tags` field. The `+page.server.ts` load function will need to supply tags — is that a new API call or does the existing document endpoint already include them? - **`DocumentBottomPanel.svelte` removal** — the issue says "remove entirely." Before deleting, we need to verify: does any route besides `documents/[id]` use it? A quick `grep` for the import will confirm. If it's only used in one place, clean deletion. If it's referenced elsewhere, those references need updating too. - **Svelte `slide` transition** — the spec calls for a Svelte slide transition. This is `transition:slide` from `svelte/transition`. Works well, but it requires the drawer to be conditionally rendered with `{#if}`, not just `display: none`. That's the correct approach anyway — no DOM when collapsed means no accessibility tree pollution. ### Suggestions - **TDD approach**: Start with a Vitest test for `MetadataDrawer.svelte` — render it with a mock document that has sender, receivers, and tags. Assert that person names render as links to `/persons/{id}`. Then test the toggle: render `DetailsToggle`, click it, assert the drawer appears. These are the two core behaviors. - **Keep the drawer data-free** — `MetadataDrawer` should receive pre-formatted props (`date: string`, `sender: Person`, `receivers: Person[]`, `tags: Tag[]`), not the raw `doc` object. This keeps the component pure and testable without needing to construct a full document object in tests. - **i18n key naming** — the proposed keys like `details_field_date` follow the pattern, but consider `doc_details_field_date` to namespace them under the document detail page and avoid collision with any future "details" sections in other contexts.</n">
Author
Owner

🏗️ Markus Keller — Application Architect

Questions & Observations

  • No backend changes needed — this is purely frontend, which is the right call. The metadata (date, sender, receivers, tags) is already loaded by the document detail +page.server.ts. No new API endpoints, no schema changes. Clean scope.

  • Removing DocumentBottomPanel.svelte — this is a breaking change for the component interface. The document detail page currently orchestrates the bottom panel. Removing it means the [id]/+page.svelte layout changes significantly. This should be done in a single commit, not spread across PRs, to avoid a half-migrated state where the drawer exists but the bottom panel is still referenced.

  • Drawer state: local vs URL — the issue says "drawer state is local (not persisted across page loads)." That's correct for now. But consider: if the user navigates to /documents/123 via a shared link, should the drawer be open by default to show context? A URL search param (?details=open) would allow this without adding complexity now. Just flagging as a future consideration, not a blocker.

  • Data flow — the 3-column grid (details, persons, tags) all comes from the same document object. No cross-domain fetching needed. The doc prop already has sender and receivers. Tags may need to be added to the document detail load if they're not already there — worth verifying before implementation starts.

Suggestions

  • Module boundary: the drawer is part of the document detail page, not a shared component. Keep it in src/lib/components/ but name it DocumentMetadataDrawer.svelte to signal it's document-scoped. Don't put it in a generic drawers/ folder — YAGNI.

  • No shared state store — the drawer's open/close state should be a simple let open = $state(false) in the parent or toggle component. No need for a Svelte store or context. Keep it as local as possible.</n">

## 🏗️ Markus Keller — Application Architect ### Questions & Observations - **No backend changes needed** — this is purely frontend, which is the right call. The metadata (date, sender, receivers, tags) is already loaded by the document detail `+page.server.ts`. No new API endpoints, no schema changes. Clean scope. - **Removing `DocumentBottomPanel.svelte`** — this is a breaking change for the component interface. The document detail page currently orchestrates the bottom panel. Removing it means the `[id]/+page.svelte` layout changes significantly. This should be done in a single commit, not spread across PRs, to avoid a half-migrated state where the drawer exists but the bottom panel is still referenced. - **Drawer state: local vs URL** — the issue says "drawer state is local (not persisted across page loads)." That's correct for now. But consider: if the user navigates to `/documents/123` via a shared link, should the drawer be open by default to show context? A URL search param (`?details=open`) would allow this without adding complexity now. Just flagging as a future consideration, not a blocker. - **Data flow** — the 3-column grid (details, persons, tags) all comes from the same document object. No cross-domain fetching needed. The `doc` prop already has sender and receivers. Tags may need to be added to the document detail load if they're not already there — worth verifying before implementation starts. ### Suggestions - **Module boundary**: the drawer is part of the document detail page, not a shared component. Keep it in `src/lib/components/` but name it `DocumentMetadataDrawer.svelte` to signal it's document-scoped. Don't put it in a generic `drawers/` folder — YAGNI. - **No shared state store** — the drawer's open/close state should be a simple `let open = $state(false)` in the parent or toggle component. No need for a Svelte store or context. Keep it as local as possible.</n">
Author
Owner

🧪 Sara Holt — QA Engineer & Test Strategist

Questions & Observations

  • Acceptance criteria are testable — good. Each one maps to a concrete assertion. But a few gaps:

    • No criterion for keyboard accessibility: can the user toggle the drawer with Enter/Space? Can they Tab through the drawer content (person links, tag chips) when open?
    • No criterion for screen reader announcement: when the drawer opens, is the state change announced? aria-expanded on the toggle button is implied by the spec but not listed as an AC.
  • Missing edge cases in acceptance criteria:

    • What if the document has no sender, no receivers, and no tags? The 3-column grid could render mostly empty. Is there an empty state or do we hide empty columns?
    • What about a document with 20+ receivers or 30+ tags? Does the drawer scroll, truncate, or wrap?
    • What if documentDate is null? The "Details" column would show "Datum: —" or omit the field entirely?
  • "Works in all modes: read, transcribe, annotate" — this is one acceptance criterion but it's really three separate test scenarios. The drawer should open/close identically regardless of mode. Worth splitting into three explicit test cases.

Suggestions

  • Test strategy for this feature:

    • Unit (Vitest): MetadataDrawer renders correct content given props. Person names are links. Tags display as chips. Empty states handled.
    • Unit (Vitest): Toggle button changes label and aria-expanded.
    • Integration: Toggle opens drawer with Svelte slide transition (test via @testing-library/svelte — assert the drawer DOM appears after click).
    • E2E (Playwright): Navigate to a document with full metadata → click "Details" → verify all fields visible → click again → verify collapsed. Run checkA11y on both states.
    • Visual regression: Capture desktop expanded, desktop collapsed, mobile expanded, mobile collapsed at 320px and 1440px.
  • Add explicit AC: "Drawer is keyboard-accessible: Enter/Space toggles, Tab navigates content, Escape closes."</n">

## 🧪 Sara Holt — QA Engineer & Test Strategist ### Questions & Observations - **Acceptance criteria are testable** — good. Each one maps to a concrete assertion. But a few gaps: - No criterion for **keyboard accessibility**: can the user toggle the drawer with Enter/Space? Can they Tab through the drawer content (person links, tag chips) when open? - No criterion for **screen reader announcement**: when the drawer opens, is the state change announced? `aria-expanded` on the toggle button is implied by the spec but not listed as an AC. - **Missing edge cases in acceptance criteria**: - What if the document has **no sender, no receivers, and no tags**? The 3-column grid could render mostly empty. Is there an empty state or do we hide empty columns? - What about a document with **20+ receivers** or **30+ tags**? Does the drawer scroll, truncate, or wrap? - What if `documentDate` is null? The "Details" column would show "Datum: —" or omit the field entirely? - **"Works in all modes: read, transcribe, annotate"** — this is one acceptance criterion but it's really three separate test scenarios. The drawer should open/close identically regardless of mode. Worth splitting into three explicit test cases. ### Suggestions - **Test strategy for this feature**: - **Unit (Vitest)**: `MetadataDrawer` renders correct content given props. Person names are links. Tags display as chips. Empty states handled. - **Unit (Vitest)**: Toggle button changes label and `aria-expanded`. - **Integration**: Toggle opens drawer with Svelte slide transition (test via `@testing-library/svelte` — assert the drawer DOM appears after click). - **E2E (Playwright)**: Navigate to a document with full metadata → click "Details" → verify all fields visible → click again → verify collapsed. Run `checkA11y` on both states. - **Visual regression**: Capture desktop expanded, desktop collapsed, mobile expanded, mobile collapsed at 320px and 1440px. - Add explicit AC: "Drawer is keyboard-accessible: Enter/Space toggles, Tab navigates content, Escape closes."</n">
Author
Owner

🔒 Nora "NullX" Steiner — Application Security Engineer

Questions & Observations

  • Person links in the drawer — person names link to /persons/{id}. These are internal navigation links, not user-generated URLs, so no open redirect risk. Good.

  • Tag chips navigate to filtered search — if the tag name is used as a query parameter (e.g. /search?tag=Familienbrief), ensure the tag name is URL-encoded and the search page treats it as a filter parameter, not as raw input injected into a query. This is likely already handled by the existing search infrastructure, but worth confirming the tag value goes through the typed API client, not string interpolation into a URL.

  • No new API endpoints, no new data exposure — this feature displays data that's already loaded on the document detail page. No new attack surface. The drawer doesn't fetch additional data, it just reorganizes what's already in the DOM. This is the ideal security profile for a UI feature.

  • Drawer content is read-only — no forms, no inputs, no user-editable content in the drawer. No XSS surface. Person names and tag names come from the backend and are rendered as text nodes, not innerHTML. Clean.

Suggestions

  • No security concerns from my angle. This is a pure presentation change with no new data flows, no new endpoints, and no user input handling. The only thing to double-check is that tag names in search URLs are properly encoded, but that's existing infrastructure, not this feature.</n">
## 🔒 Nora "NullX" Steiner — Application Security Engineer ### Questions & Observations - **Person links in the drawer** — person names link to `/persons/{id}`. These are internal navigation links, not user-generated URLs, so no open redirect risk. Good. - **Tag chips navigate to filtered search** — if the tag name is used as a query parameter (e.g. `/search?tag=Familienbrief`), ensure the tag name is URL-encoded and the search page treats it as a filter parameter, not as raw input injected into a query. This is likely already handled by the existing search infrastructure, but worth confirming the tag value goes through the typed API client, not string interpolation into a URL. - **No new API endpoints, no new data exposure** — this feature displays data that's already loaded on the document detail page. No new attack surface. The drawer doesn't fetch additional data, it just reorganizes what's already in the DOM. This is the ideal security profile for a UI feature. - **Drawer content is read-only** — no forms, no inputs, no user-editable content in the drawer. No XSS surface. Person names and tag names come from the backend and are rendered as text nodes, not `innerHTML`. Clean. ### Suggestions - No security concerns from my angle. This is a pure presentation change with no new data flows, no new endpoints, and no user input handling. The only thing to double-check is that tag names in search URLs are properly encoded, but that's existing infrastructure, not this feature.</n">
Author
Owner

🎨 Leonie Voss — UI/UX Design Lead

Questions & Observations

  • 44×28px minimum tap target for the toggle — the spec calls this out, which is good for our 60+ users. But 28px height is below the WCAG 2.2 target size recommendation of 44×44px. The width is fine at 44px+, but the height should be at least 44px including padding. Suggest min-h-[44px] on the toggle button, which gives comfortable vertical tap area without making it visually oversized (the inner text can be centered in the larger hit area with padding).

  • "Details ▼" / "Details ▲" label — using Unicode arrows (▼/▲) is good for visual affordance, but screen readers may read these as "black down-pointing triangle" which is noisy. Consider using aria-hidden="true" on the arrow character and relying on aria-expanded for the state announcement. Or use a small SVG chevron icon with aria-hidden="true".

  • 3-column grid on desktop — at 768px breakpoint, 3 columns could be tight if the "Details" column has long values (e.g. a long document title or date string). Consider whether 768px should still be single-column, with 3-column starting at 1024px. Test with real data lengths.

  • Push layout (not overlay) — excellent choice. Overlays create z-index battles with the PDF viewer and annotation layers. Push layout is also better for spatial awareness — users see the content shift, they understand where the information came from.

  • Dark mode — no mention of dark mode behavior for the drawer. The existing topbar uses bg-surface and border-line semantic tokens, which should work. But the drawer's 3-column grid, person cards, and tag chips need to use the same semantic tokens, not hardcoded whites or grays. Worth noting as an implementation constraint.

Suggestions

  • Add a subtle open/close animation on the chevron arrow itself (rotate 180°) to reinforce the toggle affordance. CSS: transition: transform 200ms ease; transform: rotate(180deg) when open.

  • The person avatar circles in the drawer should use the same color generation logic as PersonChip.svelte in the topbar — consistency matters. Don't introduce a separate color mapping.

  • Consider adding a subtle backdrop (e.g. border-bottom: 2px solid var(--brand-sand)) on the drawer's bottom edge when open, to visually separate it from the content below. The Svelte slide transition handles the motion, but the resting state needs a clear boundary.</n">

## 🎨 Leonie Voss — UI/UX Design Lead ### Questions & Observations - **44×28px minimum tap target for the toggle** — the spec calls this out, which is good for our 60+ users. But 28px height is below the WCAG 2.2 target size recommendation of 44×44px. The width is fine at 44px+, but the height should be at least 44px including padding. Suggest `min-h-[44px]` on the toggle button, which gives comfortable vertical tap area without making it visually oversized (the inner text can be centered in the larger hit area with padding). - **"Details ▼" / "Details ▲" label** — using Unicode arrows (▼/▲) is good for visual affordance, but screen readers may read these as "black down-pointing triangle" which is noisy. Consider using `aria-hidden="true"` on the arrow character and relying on `aria-expanded` for the state announcement. Or use a small SVG chevron icon with `aria-hidden="true"`. - **3-column grid on desktop** — at 768px breakpoint, 3 columns could be tight if the "Details" column has long values (e.g. a long document title or date string). Consider whether 768px should still be single-column, with 3-column starting at 1024px. Test with real data lengths. - **Push layout (not overlay)** — excellent choice. Overlays create z-index battles with the PDF viewer and annotation layers. Push layout is also better for spatial awareness — users see the content shift, they understand where the information came from. - **Dark mode** — no mention of dark mode behavior for the drawer. The existing topbar uses `bg-surface` and `border-line` semantic tokens, which should work. But the drawer's 3-column grid, person cards, and tag chips need to use the same semantic tokens, not hardcoded whites or grays. Worth noting as an implementation constraint. ### Suggestions - Add a subtle open/close animation on the chevron arrow itself (rotate 180°) to reinforce the toggle affordance. CSS: `transition: transform 200ms ease; transform: rotate(180deg)` when open. - The person avatar circles in the drawer should use the same color generation logic as `PersonChip.svelte` in the topbar — consistency matters. Don't introduce a separate color mapping. - Consider adding a subtle `backdrop` (e.g. `border-bottom: 2px solid var(--brand-sand)`) on the drawer's bottom edge when open, to visually separate it from the content below. The Svelte slide transition handles the motion, but the resting state needs a clear boundary.</n">
Author
Owner

🔧 Tobias Wendt — DevOps & Platform Engineer

Questions & Observations

  • No infrastructure impact — this is a purely frontend UI change. No new services, no new environment variables, no new Docker configuration, no new ports, no backend changes. Clean from an ops perspective.

  • No new API calls — the drawer uses data already loaded by the page. No additional network requests, no impact on backend load or response times. No caching considerations.

  • Build size impact — adding MetadataDrawer.svelte and possibly DetailsToggle.svelte adds minimal bundle size. The Svelte slide transition is already part of the Svelte runtime, so no new dependency. No concern here.

Suggestions

  • No concerns from my angle. This feature has zero operational footprint — no new services, no new config, no new dependencies, no deployment changes. Ship it.</n">
## 🔧 Tobias Wendt — DevOps & Platform Engineer ### Questions & Observations - **No infrastructure impact** — this is a purely frontend UI change. No new services, no new environment variables, no new Docker configuration, no new ports, no backend changes. Clean from an ops perspective. - **No new API calls** — the drawer uses data already loaded by the page. No additional network requests, no impact on backend load or response times. No caching considerations. - **Build size impact** — adding `MetadataDrawer.svelte` and possibly `DetailsToggle.svelte` adds minimal bundle size. The Svelte `slide` transition is already part of the Svelte runtime, so no new dependency. No concern here. ### Suggestions - No concerns from my angle. This feature has zero operational footprint — no new services, no new config, no new dependencies, no deployment changes. Ship it.</n">
Author
Owner

🎨 Leonie Voss — UI/UX Discussion Summary

Worked through 8 open UI/UX items with the team. All resolved.

Resolved items

  1. Tap target height — Update AC from "min 44×28px" to min 44×44px tap target, achieved via padding (not visual size). Keeps the button compact in the topbar while meeting WCAG 2.2 enhanced target size.

  2. SVG chevron replaces Unicode arrows — Drop ▼/▲ Unicode characters (screen readers read them as "black down-pointing triangle"). Use an SVG chevron icon from the existing De Gruyter icon set with aria-hidden="true". Animated rotation 180° on open/close (transition: transform 200ms ease). Screen readers rely on aria-expanded for state.

  3. Breakpoint simplified to two steps — Single column below 1024px, 3-column grid at ≥1024px. The original 768px breakpoint was too tight for real data (long person names, dates). No intermediate 2-column state.

  4. Empty/sparse data handling — Always show all 3 section headings (Details, Personen, Schlagwörter) even when empty. Muted empty-state text under empty sections (e.g. "Keine Personen zugeordnet"). Teaches users what metadata exists. Receivers capped at 5 visible cards with "+N weitere" expand button. Tags wrap freely, no cap. Drawer has no internal scrollbar — it pushes content down and grows as tall as needed.

  5. Dark mode AC added — New acceptance criterion: "Drawer uses semantic color tokens (bg-surface, text-ink, text-ink-2, border-line, bg-muted) — no hardcoded color values."

  6. Chevron animation — Covered by item 2. SVG chevron rotates 180° with 200ms ease transition.

  7. Drawer bottom edgeborder-bottom using border-line semantic token when drawer is open. No shadow — push layout shouldn't have overlay-style shadows.

  8. Person card hoverhover:bg-muted background highlight + cursor-pointer on the whole card. Large, obvious affordance for 60+ users. Name text doesn't need separate underline — the card-level highlight communicates clickability.

Overall read

This is a well-scoped, low-risk UI feature. The main implementation traps are hardcoded colors (dark mode breakage) and the breakpoint being too aggressive. Both are now addressed. Ship it.

## 🎨 Leonie Voss — UI/UX Discussion Summary Worked through 8 open UI/UX items with the team. All resolved. ### Resolved items 1. **Tap target height** — Update AC from "min 44×28px" to **min 44×44px tap target**, achieved via padding (not visual size). Keeps the button compact in the topbar while meeting WCAG 2.2 enhanced target size. 2. **SVG chevron replaces Unicode arrows** — Drop ▼/▲ Unicode characters (screen readers read them as "black down-pointing triangle"). Use an **SVG chevron icon** from the existing De Gruyter icon set with `aria-hidden="true"`. Animated rotation 180° on open/close (`transition: transform 200ms ease`). Screen readers rely on `aria-expanded` for state. 3. **Breakpoint simplified to two steps** — Single column below 1024px, 3-column grid at ≥1024px. The original 768px breakpoint was too tight for real data (long person names, dates). No intermediate 2-column state. 4. **Empty/sparse data handling** — Always show all 3 section headings (Details, Personen, Schlagwörter) even when empty. Muted empty-state text under empty sections (e.g. "Keine Personen zugeordnet"). Teaches users what metadata exists. Receivers capped at 5 visible cards with "+N weitere" expand button. Tags wrap freely, no cap. Drawer has no internal scrollbar — it pushes content down and grows as tall as needed. 5. **Dark mode AC added** — New acceptance criterion: "Drawer uses semantic color tokens (`bg-surface`, `text-ink`, `text-ink-2`, `border-line`, `bg-muted`) — no hardcoded color values." 6. **Chevron animation** — Covered by item 2. SVG chevron rotates 180° with 200ms ease transition. 7. **Drawer bottom edge** — `border-bottom` using `border-line` semantic token when drawer is open. No shadow — push layout shouldn't have overlay-style shadows. 8. **Person card hover** — `hover:bg-muted` background highlight + `cursor-pointer` on the whole card. Large, obvious affordance for 60+ users. Name text doesn't need separate underline — the card-level highlight communicates clickability. ### Overall read This is a well-scoped, low-risk UI feature. The main implementation traps are hardcoded colors (dark mode breakage) and the breakpoint being too aggressive. Both are now addressed. Ship it.
Sign in to join this conversation.
No Label feature ui
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#175