Die rechte Spalte der Startseite ist mit DropZone + „Metadaten fehlen" bereits ausgelastet. Issue #240 möchte zwei weitere Karten ergänzen: Transkription ausstehend und Lesefertig. Direkt gestapelt scrollt die Spalte sofort aus dem sichtbaren Bereich — ein 67-jähriger Nutzer auf einem kleinen Display sieht die unteren Karten nie.
Der Nutzer schlug Tabs vor (Neueste Aktivität / Transkription fehlt / Metadaten fehlen / Lesefertig). Diese Spec bewertet diesen Vorschlag und zeigt drei weitere Muster im Vergleich. Alle Muster werden bei 320 px (Mobiltelefon) und Desktop (~55 % Skalierung) gezeigt.
Der ursprüngliche Vorschlag nannte vier Tabs für die gesamte Seite (Neueste Aktivität / Transkription / Metadaten / Lesefertig). Das ist ein UX-Antipattern: „Neueste Aktivität" ist der häufigste Anwendungsfall — ihn hinter einem Klick zu verstecken erhöht den Aufwand für jeden Dashboard-Besuch. Tabs sollten ausschließlich auf die drei To-do-Widget-Kategorien in der rechten Spalte angewendet werden, nicht auf die gesamte Seite.
Accessibility: Tab-Elemente müssen role="tablist", role="tab", aria-selected und
role="tabpanel" tragen. Jeder Tab braucht min-h-[44px] (WCAG 2.2 Touchziel).
Drei Tabs in einer 300-px-Spalte = ~100 px pro Tab — grenzwertig auf Deutsch mit langen Wörtern.
| Element | Tailwind-Klassen | Wert | Hinweis |
|---|---|---|---|
| Tab-Strip | flex border-b border-line | — | ARIA: role="tablist" |
| Tab inaktiv | px-3 py-3 text-xs font-medium text-gray-500 border-b-2 border-transparent whitespace-nowrap -mb-px | min-h 44 px ✓ | role="tab" aria-selected="false" |
| Tab aktiv | px-3 py-3 text-xs font-semibold text-ink border-b-2 border-ink -mb-px | 2 px navy Unterlinie | aria-selected="true" |
| Tab-Panel | pt-3 focus:outline-none | — | role="tabpanel" tabindex="0" |
| Aufgaben-Karte | rounded-sm border border-line bg-white p-4 flex-1 | padding 16 px | Ersetzt 3 separate Karten |
| Zeile mit Kontext | flex flex-col py-2 border-b border-line last:border-0 | min-h ~44 px | z. B. „3 von 8 Blöcken geprüft" |
Jede Sektion zeigt eine klickbare Kopfzeile (Pfeil + Label + Anzahl). Server-seitig wird die Sektion mit der
höchsten Anzahl als <details open> gerendert. Kein Client-JS nötig — native
<details>/<summary>-Elemente liefern ARIA-Accessibility gratis.
Sortierung der offenen Sektion nach Dringlichkeit: Metadaten → Transkription → Lesefertig.
| Element | HTML / Tailwind-Klassen | Wert | Hinweis |
|---|---|---|---|
| Accordion-Wrapper | <details> nativ | — | Accessibility gratis, kein JS |
| Accordion-Header | <summary class="flex items-center justify-between min-h-[44px] cursor-pointer list-none"> | min-h 44 px ✓ | WCAG 2.2 Touchziel |
| Pfeil-Icon | transition-transform group-open:rotate-90 | w-4 h-4 | CSS-only; kein JS |
| Zähler-Badge | ml-auto font-mono text-xs text-gray-400 | — | z. B. „(5)" |
| Dringlichste Sektion | <details open> | — | Server-seitig rendern: if incompleteDocs.length >= needsTrans.length |
| Accordion-Inhalt | pt-1 pb-2 direkt nach <summary> | — | Keine overflow:hidden-Animation nötig |
Der Ist-Zustand der rechten Spalte bleibt vollständig erhalten. Die zwei neuen Karten des Issue #240 werden
nicht in die rechte Spalte gepackt, sondern in einen neuen vollbreiten Abschnitt direkt unterhalb des
bestehenden Zwei-Spalten-Gitters. Der Abschnitt ist nur sichtbar, wenn mindestens eine der beiden Kategorien
Einträge hat ({#if needsTranscription.length > 0 || readyToRead.length > 0}).
Die „Lesefertig"-Spalte erhält einen mint-gefärbten Hintergrund (bg-mint/10 border-mint) als positives Signal — kein
neutrales To-do, sondern eine Einladung zum Lesen. Leere Zustände zeigen eine kurze Erfolgsmeldung in
bg-mint/5, nicht eine tote weiße Box.
| Element | Tailwind-Klassen | Wert | Hinweis |
|---|---|---|---|
| Streifen-Wrapper | mt-4 bg-white border border-line rounded-sm p-6 | padding 24 px | Direkt nach bestehendem div.mt-4.grid |
| Streifen-Titel | text-xs font-bold uppercase tracking-widest text-gray-400 mb-4 | 12 px / 700 | Standard-Section-Title-Muster |
| 3-Spalten-Grid | grid grid-cols-1 gap-4 sm:grid-cols-3 | gap 16 px | Mobil: 1 Spalte, sm+: 3 |
| Transkription-Spalte | bg-surface rounded-sm border border-line p-4 | — | Neutral — es ist eine Aufgabe |
| Lesefertig-Spalte | bg-mint/10 rounded-sm border border-mint p-4 | — | Mint-Ton = positives Signal |
| Leerer Zustand | flex flex-col items-center justify-center text-center bg-mint/5 border border-dashed border-mint rounded-sm p-6 min-h-[80px] | min-h 80 px | Niemals leere graue Box |
| Untertext-Zeile | text-xs text-gray-400 mt-0.5 | 12 px | z. B. „3 von 8 Blöcken geprüft" |
| Sichtbarkeit | {#if needsTranscription.length > 0 || readyToRead.length > 0} | — | Streifen komplett ausgeblendet wenn leer |
Alle drei Kategorien werden in einer einzigen sortierten Liste zusammengeführt. Sortierung: Metadaten fehlen (blockiert Suche) → Transkription fehlt → Lesefertig. Jede Zeile trägt ein farbkodiertes Label; Farbe darf niemals der einzige Indikator sein — Icon und Text sind Pflicht (WCAG 1.4.1).
Kontrast-Check: Orange-700 auf Weiß = 5,4:1 ✓ AA. Navy auf Weiß = 14,5:1 ✓ AAA. Green-800 auf Weiß = 9,7:1 ✓ AAA.
| Element | Tailwind-Klassen | Wert | Hinweis |
|---|---|---|---|
| Listen-Wrapper | rounded-sm border border-line bg-white p-4 flex-1 | — | Ersetzt separate 3 Karten |
| Prioritäts-Zeile | flex items-start gap-3 py-2 border-b border-line last:border-0 min-h-[44px] | min-h 44 px ✓ | WCAG touch target |
| Typ-Punkt | w-2 h-2 rounded-full mt-1.5 shrink-0 | 8 × 8 px | Nie allein — Label ist Pflicht |
| Label orange | text-xs text-orange-700 | 12 px | Kontrast 5,4:1 ✓ AA |
| Label navy | text-xs text-ink | 12 px | Kontrast 14,5:1 ✓ AAA |
| Label grün | text-xs text-green-800 | 12 px | Kontrast 9,7:1 ✓ AAA |
| Merge-Service | findWhatsNext(int size) auf DashboardController | — | Sortierung: Metadaten → Trans → Lesefertig; per Markus: Threshold als @Param |
| Kriterium | A — Tabs | B — Accordion | C — Mission Control ★ | D — Priority Queue |
|---|---|---|---|---|
| Alle 3 Kategorien sofort sichtbar | Nein | Nur Überschriften | Ja | Nein (gemischt) |
| Neueste Aktivität bleibt Primärinhalt | Ja | Ja | Ja | Ja |
| 60+ Usability (Discovery ohne Klick) | Mittel | Überschriften sichtbar | Sehr gut | Mittel — gemischte Liste |
| JS-Zustand nötig | Ja (aktiver Tab) | Nein | Nein | Nein |
| WCAG 2.2 compliance (out of the box) | tablist + aria-selected nötig | details/summary nativ | Keine neuen Anforderungen | Farbe + Icon + Label alle Pflicht |
| Mobile 320 px | 3 Tabs zu schmal | Gut | Sehr gut — stapelt natürlich | Gut |
| „Lesefertig" als visueller Applaus | Nur wenn Tab aktiv | Nur wenn offen | Ja — eigene mint-Karte | Nein — gleichwertig mit Aufgaben |
| Backend-Merge-Komplexität | Gering | Gering | Gering (2 separate Queries) | Mittel (Merge + Sortierung) |
| Implementierungsaufwand Frontend | Mittel | Gering | Gering | Gering |
Der Mission-Control-Streifen ist das einzige Muster, das alle drei Kategorien gleichzeitig sichtbar macht, ohne den Primärinhalt zu verstecken oder JS-Zustand zu erzeugen. Scrollen nach unten ist kein Fehler — versteckter Inhalt schon.
Quick win: Wenn C abgelehnt wird — Muster B (Accordion) als Zweitstimme. Kein Refactoring der rechten Spalte, kein JS, alle Kategorien-Überschriften immer sichtbar.
SELECT … WHERE annotation_count = 0text IS NULL OR LENGTH(text) < threshold). Kurrent-Kenntnisse empfohlen.
annotation_count > 0 AND reviewed < 75 %reviewed_pct >= 0.90 (bestehend)COUNT(*) WHERE created_at > NOW() - INTERVAL '7 days'| Element | Tailwind-Klassen / Logik | Wert | Hinweis |
|---|---|---|---|
| Skill-Pill „Ohne Vorkenntnisse" | inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-green-50 border border-green-200 text-green-800 | Kontrast 9,7:1 ✓ | Klärung für 60+ und Neueinsteiger |
| Skill-Pill „Kurrent hilfreich" | inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-surface border border-line text-ink | neutral — kein Schreckpunkt | Nicht „Experten nötig" sondern „hilfreich" |
| Wochenpuls (Segmentierung + Transkription) | text-xs font-semibold text-green-700 (Seg.) / text-ink (Trans.) | 12 px | Query: COUNT WHERE created_at > NOW() - INTERVAL '7 days'; kein globaler Balken |
| Per-Dokument-Balken Track | flex-1 h-1 bg-navy/20 rounded-full overflow-hidden | h: 4 px | Nur in Transkription-Spalte, nur wenn annotation_count > 0 |
| Per-Dokument-Balken Füllstand | h-full bg-navy rounded-full + style="width:{pct}%" | — | pct = textedBlocks / totalBlocks * 100; Guard: totalBlocks = 0 → width 0 |
| Lesefertig-Prozentzahl | text-xs font-semibold text-green-800 | 12 px | Kein Balken — die mint-Spalte selbst ist das Erfolgssignal |
| Contributor-Avatar | w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white | 24 × 24 px | Farbe per User-ID deterministisch (kein API-Feld nötig) |
| „Starte hier"-CTA | block w-full text-center text-xs font-semibold text-white bg-ink rounded-sm py-1.5 mt-2 hover:bg-ink-2 transition-colors focus-visible:ring-2 focus-visible:ring-ink | min-h 36 px | Link: /enrich?filter=NEEDS_SEGMENTATION&next=1 |
| Lesefertig-Leerstand CTA | inline-flex items-center text-xs font-semibold text-ink border border-ink rounded-sm px-3 py-1 hover:bg-ink hover:text-white transition-colors | — | Link springt zur Segmentierungs-Ansicht |
| Contributor-API-Feld | GET /api/documents/needs-segmentation → DTO enthält lastContributors: [{initials, colorSeed}] | max 3 Avatare | Neues DTO-Feld — beachte Nora: nur Initialen, keine Namen |
| Segmentierung-Query | WHERE NOT EXISTS (SELECT 1 FROM document_annotations WHERE document_id = d.id) | — | Index auf document_annotations.document_id prüfen (Tobias) |
| Transkription-Query | EXISTS annotation AND (no blocks OR reviewed_pct < 0.75) | — | Guard gegen Division durch 0 (Sara) |
initials + einen deterministischen colorSeed (z. B. Hash der User-ID mod 6 Farben),
keine E-Mail-Adressen oder echten Namen. Das @RequirePermission(READ_ALL) auf den neuen Endpoints gilt auch hier.