MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m38s
VoiceInputButton brings the Quick Dump microphone into other input rows (useVoiceInput, appends to the text already there, hidden when voice input is off or unsupported): the add-task row above the task list and the add-item field of lists (desktop row and phone sheet). Column dialog on phones: the add-column buttons go below the field, and the Erledigt toggle of a column shows only its tick, so the bin stays on screen. Docs and changelog; v0.32.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.1 KiB
9.1 KiB
UI-Muster
Wiederkehrende Bedienelemente, die überall gleich aussehen und sich gleich verhalten. Wer eines davon neu braucht, nimmt die vorhandene Komponente und baut keine eigene Variante.
Personen suchen (Type-to-Search)
Komponente: UserAutocomplete.tsx
Eingesetzt beim Teilen (UnifiedShareModal), beim Hinzufügen zu Gruppen (GroupsView) und beim Zuweisen von Aufgaben (TaskDetailModal).
- Keine Liste vorab: Solange das Feld leer ist, erscheint nichts, auch nicht beim Reinklicken. Kein Dropdown mit allen Personen.
- Erst beim Tippen erscheinen Treffer direkt unter dem Feld, höchstens 3 (
MAX_RESULTS). Gesucht wird in Anzeigename,@Benutzernameund E-Mail. - Der erste Treffer ist vorausgewählt: Tippen + Enter übernimmt ihn. Pfeiltasten wechseln, Escape schließt.
- Bereits gewählte oder freigegebene Personen werden über
excludeUserIdsausgeblendet. - Einzelauswahl (Teilen, Gruppen):
selectedUsersetzen; das Feld zeigt dann die gewählte Person mit ×. - Mehrfachauswahl (Aufgaben zuweisen):
selectedUser={null}lassen und inonSelectUserzur Liste hinzufügen. Die Gewählten stehen als Chips (Avatar, Name, ×) über dem Feld, das Feld bleibt für die nächste Suche frei.
Profilbilder als Stapel
Komponente: AvatarStack.tsx (nutzt UserAvatar)
- Kleine runde Profilbilder, leicht überlappend, höchstens 3. Weitere Personen erscheinen als monochromer Kreis „+n“.
- Ohne Profilbild zeigt
UserAvatardie Initialen. ringClassbekommt die Farbe der Fläche dahinter (meistring-[var(--bg-panel)]oderring-[var(--bg-app)]), damit die Kreise sauber getrennt sind.- Größen:
xxs(16 px) in Tabs, Kanban-Karten und Listeneinträgen,xs(20 px) in Aufgabenzeilen und Kopfzeilen. - Kein Hover-Effekt im Stapel selbst. Einzige Ausnahme ist die Anwesenheit in Notizen (PresenceAvatars.tsx): Dort zeigt Hover oder Tastaturfokus die Liste „Gerade online“.
| Ort | Wer wird gezeigt |
|---|---|
| Notiz-Kopfzeile | wer die Notiz gerade geöffnet hat (Profilbilder aus /users/directory) |
| Tabs der Aufgabenlisten und Listen | alle mit Zugriff außer mir (members) |
| Aufgabenzeile und Kanban-Karte | zugewiesene Personen (nur in geteilten Listen) |
| Eintrag einer geteilten Liste | wer ihn hinzugefügt hat (createdBy); ältere Einträge ohne Autor zeigen nichts |
Schalter
Komponente: ToggleSwitch.tsx
- Ein/Aus-Einstellungen sind immer Schalter, keine Checkbox-Haken und keine „angehakten“ Buttons (Einrichtung, Admin-Bereich, Berechtigungen eines API-Keys, Bereiche beim Import und Wiederherstellen →
SectionPicker). - Text links (Titel, darunter eine kurze Erklärung in
text-muted), Schalter rechts. - Eine Auswahl einer Option aus mehreren (Import-Modus, Ecken, Theme-Folgen) bleibt eine Button-Gruppe mit
role="radio".
Kleine Dialoge (Anlegen, Umbenennen)
Komponente: FormSheet.tsx, Tastatur-Abstand über useKeyboardInset.ts
- Handy: Bottom-Sheet über die volle Breite, das direkt über der Bildschirmtastatur sitzt (der Abstand kommt aus
visualViewport), Griff oben, Felder und Buttons mindestens 44 px hoch, Eingaben in 16 px (sonst zoomt iOS), Abbrechen/Erstellen nebeneinander über die ganze Breite. - Desktop: zentriertes Fenster, oder mit
anchorneben der Klickposition (z. B. „Neuer Ordner“ im Explorer). - Escape und Klick auf den Hintergrund schließen. Genutzt für neue Aufgabenliste, neue Einkaufsliste und neuen Ordner; das Emoji-Sheet nutzt denselben Tastatur-Abstand.
Zähler
Komponente: CountBadge.tsx, CSS-Klassen count-badge* in index.css. Es gibt keine anderen Zähler-Stile mehr.
- Form: runder Kreis, mindestens 16 px, ab 100 als „99+“ (der Screenreader liest die echte Zahl). Bei 0 ausgeblendet, außer mit
showZero(z. B. Mitgliederzahl, mobiles Menü). - Farbe (
variant):accent: Hier wartet etwas auf dich. Navigationsleiste: Triage (Inbox), Aufgaben (offene Aufgaben), Listen (offene Einträge aller eigenen und geteilten Listen). Workspace hat keinen Zähler.muted(Standard): reine Anzahl. Tabs der Aufgabenlisten und Listen, Organisationen, mobiles Menü.- Beide kommen aus den Theme-Variablen (
--accent-primary/--text-on-accentbzw.--text-muted/--bg-panel) und passen sich damit jedem Theme an.
- Position (
position):corner: Benachrichtigungs-Badge oben rechts am nächstenrelativeElternelement, mit Ring in der Seitenfarbe. In der Navigation und an den Tabs sitzt er auf dem Text: der Text steht in einemrelative inline-flex-Span (mitpr-3, solange ein Zähler da ist), der Kreis bekommt-top-0.5 -right-2, damit er die obere Tab-Linie nicht anschneidet und nicht am Text klebt. In der mobilen Leiste sitzt er am Icon (-top-1.5 -right-2.5).inline: im Textfluss.
- Zahlen im mobilen Menü (
MobileActionContext,badge: number) werden automatisch alsCountBadgegezeigt; Text-Badges wie „2 aktiv“ bleiben Textmarken. - Wichtig für neue Klassen: Tailwind entfernt Klassen aus
@layer components, die nicht wörtlich im Code stehen. Klassennamen deshalb nie zusammensetzen (count-badge-${variant}), sondern ausschreiben.
Herkunfts-Badge (API-Schlüssel)
Komponente: OriginBadge.tsx
- Eine Badge für alles, was ein API-Schlüssel angelegt oder geändert hat (Feld
origin): neutraler Rahmen, vorne ein Symbol für den Zugang (Webhook = REST/curl, Roboter = MCP), dahinter der Name des Schlüssels. Ältere Einträge ohne Schlüssel zeigen „API“ bzw. „MCP“. - Höchstens eine Badge pro Eintrag: der Schlüssel, der angelegt hat, sonst der, der zuletzt geändert hat. Sie bleibt dauerhaft, auch nach eigenem Bearbeiten und nach der Triage.
- Details (angelegt / zuletzt geändert, mit Schlüssel und Zeit) per Mouseover und per Tippen (kleines Info-Fenster, am Handy mit vergrößerter Trefferfläche); in der Aufgaben-Detailansicht stehen sie zusätzlich unten (
originDetails). Ein Tipp auf die Badge öffnet nie den Eintrag. - Größen:
smin Aufgabenzeilen, Inbox-Liste und Detailansicht,xsauf Kanban-Karten und in Listen,mdauf den Triage-Karten.
Mikrofon in Eingabezeilen
Komponente: VoiceInputButton.tsx (nutzt useVoiceInput wie der Quick Dump)
- Runder Mikrofon-Knopf direkt in der Zeile, rechts im Feld; diktierter Text wird an den Text angehängt, der beim Start im Feld stand. Am Handy 36 px, am PC 28 px.
- Während der Aufnahme
MicOffin Akzentfarbe, mit eigenem Whisper-Server zusätzlich die Live-Wellen, danach „Wird erkannt…“ mit Spinner. - Ausgeblendet, wenn die Spracheingabe aus ist (
STT_DISABLED) oder der Browser sie nicht kann (z. B. Firefox ohne eigenen Whisper-Server). Fehler zeigt das Feld kurz als Platzhalter. - Eingesetzt: Quick Dump (eigene Umsetzung), Aufgabe hinzufügen (Zeile am PC, „Neue Aufgabe anlegen“ am Handy), Listeneintrag hinzufügen.
Ladebildschirm & Statusleiste
manifest.json:name„MindSift“ (kurz, der Slogan steht indescription),background_colorundtheme_color#2563eb– Android baut daraus den Splash-Screen.index.htmlenthält in#rooteinen reinen HTML-Platzhalter (vollflächig Blau, Logo in der Mitte, kein Text). React ersetzt ihn beim ersten Rendern, dazwischen blitzt nichts Weißes auf.meta theme-colorstartet blau und wird vontheme.tsnach dem Start und bei jedem Theme-Wechsel auf--bg-appdes aktiven Themes gesetzt, damit die Statusleiste zum Header passt.
+-Button: Tippen und Halten
Komponente: HoldToRecordFab.tsx
- Kurz tippen (< 350 ms): Führt wie bisher die primäre Aktion der aktuellen Ansicht (
primaryAction) oder den mobilen Erfassungsdialog (openMobileTriageCapture) aus. - Gedrückt halten (≥ 350 ms): Startet direkt eine Sprachaufnahme über
useVoiceInput(im Server-Modus per Whisper mit echten Pegel-Wellen, sonst über die Spracherkennung des Browsers). Der Button wird zum pulsierenden Mikrofon (bg-status-err), darüber zeigt eine schwebende Pille „Ich höre zu… (loslassen speichert)“ samt Live-Transkript. - Loslassen: Stoppt die Aufnahme, wartet kurz auf das letzte Wort und speichert den Text unabhängig von der aktiven Ansicht immer als Gedanken in die Inbox (
source: 'voice',target: 'inbox'). - Nach oben ziehen (> 60 px): Schaltet die Pille in den Abbruch-Zustand („🗑️ Zum Abbrechen loslassen“); Loslassen dort verwirft die Aufnahme (
abort()). - Fallback: Ist die Spracheingabe abgeschaltet (
STT_DISABLED=true), unterstützt der Browser keine Spracherkennung oder ist das Mikrofon gesperrt (not-allowed), öffnet Halten wie beim Tippen den Dialog und blendet eine kurze Statusmeldung über der Bottom-Leiste ein.