Blog
Feature
15. Oktober 20259 min

Favoriten, Zuweisung und Anhänge in TAMSIV

Es gibt die großen Features und es gibt die "kleinen" Ergänzungen, die das Produkt wirklich nutzbar machen. Favoriten, Aufgabenzuweisung, Anhänge: Jedes scheint auf dem Papier trivial. In der Praxis hat mich jedes vor architektonische Entscheidungen gestellt, die jahrelang Konsequenzen haben werden.

Wenn du eine App alleine entwickelst, ist jede technische Entscheidung ein Wagnis. Du hast kein Team zum Diskutieren, keinen CTO zur Validierung. Du triffst die Wahl, du übernimmst die Verantwortung, du lebst damit. Diese drei Features haben mich gezwungen, grundlegende Fragen zu klären: JSONB oder relationale Tabellen? Einfacher oder kombinatorischer Filter? Permanente oder signierte URL? Hier sind die Hintergründe dieser Entscheidungen.

Schlüsselpunkte
  • Ein einfacher Favorit (ein Boolescher Wert) erfordert eine DB-Spalte, RLS, Feed-Filter, Animation und Echtzeit-Synchronisation, um produktionsreif zu sein.
  • Die Aufgabenzuweisung verwendet eine Verknüpfungstabelle collaborative.task_assignments in Kombination mit einer 3-Modus-Filterleiste für maximale Flexibilität.
  • Anhänge verwenden relationale Tabellen anstelle von JSONB, eine architektonische Entscheidung, die Leistung und Skalierbarkeit fördert.
  • Signierte Supabase Storage URLs laufen nach 1 Stunde ab: Ein automatischer Batch-Refresh über storage_path löst das Problem transparent.
Smartphone-Bildschirm mit einer Aufgabenliste und einer glänzenden goldenen Sternanimation auf einer Karte, dunkles Thema mit blauen Akzenten

Warum dauert die Implementierung eines einfachen Favoriten so lange?

Eine Aufgabe als Favorit markieren, das ist ein Toggle. Ein Boolescher Wert. is_favorite: true/false. Das sollte 30 Minuten dauern. In der Praxis habe ich anderthalb Tage dafür gebraucht. Hier ist der Grund.

Zuerst die Spalte in der Datenbank. Einen Booleschen Wert zu privat.tasks hinzuzufügen, ist schnell. Aber du musst auch die RLS (Row Level Security) aktualisieren, damit nur der Eigentümer seinen Favoriten ändern kann. Dann muss der Feed die Änderung widerspiegeln: Favoritenaufgaben müssen gefiltert werden können. Das bedeutet, die RPC get_consolidated_feed zu ändern, um einen zusätzlichen Filterparameter zu akzeptieren.

Dann kommt die Animation. Ein Stern, der leblos erscheint, ist langweilig. Ich habe einen Bounce mit Animated.spring implementiert: Der Stern wird leicht über seine endgültige Größe hinaus größer und kehrt dann zurück. Es ist subtil, insgesamt 200 Millisekunden, aber es gibt ein zufriedenstellendes Feedback. Studien der Nielsen Norman Group zu Mikrointeraktionen zeigen, dass diese Feedback-Animationen die Benutzerzufriedenheit erheblich verbessern.

Schließlich die Echtzeit-Synchronisation. Wenn du eine Aufgabe auf deinem Telefon als Favorit markierst, muss die Änderung sofort auf dem Web-Dashboard sichtbar sein. Dank des ContentCacheService und seines Supabase Realtime Kanals geschieht dies automatisch. Aber es musste sichergestellt werden, dass das Realtime-Ereignis das Feld is_favorite im Payload enthält.

30 Minuten auf dem Papier. Anderthalb Tage in Wirklichkeit. Das ist der Unterschied zwischen "implementieren" und "richtig implementieren".

Wie funktioniert das Aufgaben-Zuweisungssystem?

Vogelperspektive auf einen modernen Schreibtisch mit mehreren Geräten, die eine kollaborative Aufgabenverwaltungsoberfläche anzeigen, Team-Avatare sichtbar

Die Zuweisung ist das Herzstück der Zusammenarbeit. Du erstellst eine Aufgabe in einer Gruppe und weist sie einem Mitglied zu. Das Datenmodell basiert auf einer Verknüpfungstabelle: collaborative.task_assignments. Eine Aufgabe kann mehreren Personen zugewiesen werden. Eine Person kann mehrere Aufgaben zugewiesen bekommen.

Die eigentliche technische Herausforderung ist nicht die Tabelle. Es ist der FilterBar. Die Oberfläche bietet drei Filtermodi:

  • Alle: alle Aufgaben der Gruppe, unabhängig vom Autor oder Zugewiesenen.
  • Von mir erstellt: nur die Aufgaben, die ich erstellt habe, einschließlich derer, die anderen zugewiesen wurden.
  • Mir zugewiesen: nur die Aufgaben, die mir zugewiesen wurden, von anderen oder von mir selbst erstellt.

Diese drei Modi lassen sich mit dem hierarchischen Gruppenfilter kombinieren. Wenn du eine Gruppe "Unternehmen" mit Untergruppen "Marketing", "Entwicklung", "Design" hast, kannst du die dir zugewiesenen Aufgaben in der Gruppe "Unternehmen" und all ihren Untergruppen sehen, oder nur in "Marketing". Ich hatte diese hierarchische Architektur im Artikel über hierarchische Gruppen detailliert beschrieben.

Die resultierende SQL-Abfrage ist ein Join zwischen privat.tasks, collaborative.task_assignments und collaborative.groups mit einer rekursiven CTE für die Hierarchie. Gemäß der PostgreSQL-Dokumentation zu rekursiven CTEs ist dies der empfohlene Ansatz für Baumstrukturen. Die Leistung bleibt dank der Indizes auf den Fremdschlüsseln ausgezeichnet.

Warum relationale Tabellen anstelle von JSONB für Anhänge wählen?

Dies ist eine grundlegende architektonische Entscheidung. Mir standen zwei Ansätze zur Verfügung, um Anhänge (Fotos, Videos, Dokumente, die an Aufgaben und Memos angehängt sind) zu speichern:

Option A: JSONB. Einfach und schnell. Ein Feld attachments JSONB direkt in der Tabelle privat.tasks. Keine Joins, keine zusätzliche Tabelle. Du serialisierst ein Array von Objekten und fertig.

Option B: Relationale Tabellen. Zwei dedizierte Tabellen: privat.task_attachments und privat.memo_attachments. Jeder Anhang ist ein Datensatz mit eigenen Spalten: storage_path, file_name, file_type, file_size, created_at.

Ich habe Option B gewählt. Hier ist der Grund:

  1. Abfrageleistung: Das Suchen nach "allen Bildern über 5 MB" in einem JSONB erfordert ein jsonb_array_elements gefolgt von einem Cast. Mit einer relationalen Tabelle ist es ein einfaches WHERE file_size > 5000000 AND file_type LIKE 'image/%'.
  2. Kaskadierendes Löschen: Wenn du eine Aufgabe löschst, bereinigt das ON DELETE CASCADE auf dem Fremdschlüssel automatisch die Anhänge. Mit JSONB musst du die Bereinigung manuell verwalten.
  3. Individuelle RLS: Jeder Anhang hat seine eigenen Zugriffsregeln. Du kannst einer Gruppe das Lesen eines Anhangs erlauben, ohne Zugriff auf die gesamte Aufgabe zu gewähren. Mit JSONB unmöglich.
  4. Skalierbarkeit: Das Hinzufügen von Metadaten (Bildabmessungen, Videodauer, Miniaturansicht) erfolgt mit einem einfachen ALTER TABLE ADD COLUMN. Mit JSONB änderst du ein implizites Schema ohne DB-seitige Validierung.

Gemäß den PostgreSQL-Empfehlungen zu JSONB ist dieser Typ ideal für semistrukturierte Daten, deren Schema unvorhersehbar ist. Anhänge haben ein perfekt vorhersehbares Schema. Die Wahl war klar.

Was ist die Falle bei signierten Supabase Storage URLs?

Abstrakte Visualisierung eines Datenbankschemas mit miteinander verbundenen Knoten und Datenflüssen, holografische blaue und violette Töne

Supabase Storage verwendet signierte URLs, um den Zugriff auf Dateien zu sichern. Du kannst nicht direkt über eine öffentliche URL auf die Datei zugreifen: Du musst eine temporäre URL anfordern, die mit einem Token signiert ist und nach einer konfigurierbaren Zeit (standardmäßig 1 Stunde) abläuft.

Das ist ausgezeichnet für die Sicherheit. Es ist ein Albtraum für die UX, wenn du es nicht richtig handhabst. Stell dir vor: Ein Benutzer öffnet seinen Feed, sieht die Bilder seiner Aufgaben. Er lässt die App 2 Stunden lang geöffnet. Er scrollt: Die Bilder zeigen 403-Fehler an. Die URL ist abgelaufen. Das Bild ist immer noch im Speicher, aber der Link zum Zugriff ist nicht mehr gültig.

Meine Lösung: StorageService.refreshAttachmentUrlsBatch(). Diese Methode nimmt ein Array von storage_path (die permanenten Pfade im Supabase-Bucket) und generiert die signierten URLs im Batch neu. Der Schlüsselpunkt: Wir speichern niemals die signierte URL als Quelle der Wahrheit. Wir speichern den storage_path und generieren die signierte URL bei Bedarf.

Der Refresh wird in drei Fällen ausgelöst:

  • Beim Laden einer Liste: Die URLs werden im Batch für alle sichtbaren Anhänge generiert.
  • Beim Pull-to-Refresh: Der Benutzer erzwingt die Aktualisierung.
  • Nach dem Zurückkehren in den Vordergrund: Wenn die App länger als eine Stunde im Hintergrund war, werden die URLs neu generiert.

Das ist die Art von Detail, das unsichtbar ist, wenn es funktioniert, und katastrophal, wenn es kaputtgeht. Ich hatte die gleiche Art von Herausforderung mit dem Feed-Cache und der Gamifizierung: Die Daten existieren, aber ihre Anzeige hängt von einem zuverlässigen Aktualisierungsmechanismus ab.

Wie wurde die Stern-Favoriten-Animation konzipiert?

Die Sternanimation ist ein gutes Beispiel für eine Mikrointeraktion, die den Unterschied macht. Das Prinzip ist einfach: Wenn du auf den Stern tippst, wechselt er von leer zu voll mit einem Rebound-Effekt.

Technisch gesehen ist es ein Animated.spring mit einem toValue von 1.3 (30% Überschwingen) und dann einem Rücksprung auf 1.0. Das useNativeDriver: true stellt sicher, dass die Animation auf dem nativen Thread läuft, nicht auf der JavaScript-Bridge. Das ist die gleiche Philosophie wie beim KI-Nebel-Button: Animationen müssen auch auf Einsteigergeräten flüssig sein.

Ich habe einen leichten haptischen Effekt auf iOS (über ReactNativeHapticFeedback) hinzugefügt, der mit dem Höhepunkt des Bounces synchronisiert ist. Auf Android ist das haptische Feedback je nach Hersteller weniger zuverlässig, daher habe ich mich für einen sofortigen Farbwechsel entschieden: Der Stern wechselt von Grau zu Gold ohne Farbübergang, nur die Größe wird animiert.

Der Unterschied zwischen einer Amateur-App und einer professionellen App liegt oft in diesen Details. Jede Interaktion muss ein Feedback geben. Der Benutzer muss spüren, dass die App seine Aktion verstanden hat, bevor der Server dies bestätigt.

Wie geht der FilterBar mit kombinatorischer Komplexität um?

Der FilterBar ist wahrscheinlich die am meisten unterschätzte Komponente von TAMSIV. Oberflächlich betrachtet ist es eine Reihe von Schaltflächen. Darunter verbirgt sich ein kombinatorisches Filtersystem, das 3 Kontexte (Privat / Geteilt / Alle) multipliziert mit 3 Modi (Alle / Von mir erstellt / Mir zugewiesen) multipliziert mit N Gruppen mit Hierarchie verwaltet.

Die Komponente HierarchicalGroupPicker zeigt die Gruppenhierarchie mit einem Umschalter "Untergruppen einschließen" an. Wenn du eine übergeordnete Gruppe mit aktivierter Inklusion auswählst, verwendet die Abfrage eine rekursive CTE, um alle untergeordneten IDs abzurufen. Ich hatte diese Architektur im Artikel über hierarchische Gruppen dargelegt.

Die Hauptaufgabe ist die Leistung. Jede Filteränderung löst eine neue Abfrage aus. Wenn der Benutzer schnell auf mehrere Filter tippt, wollen wir nicht 5 gleichzeitige Abfragen. Ich habe einen Debounce von 300 ms implementiert: Nur der letzte Filterzustand löst die Abfrage aus. Das Ergebnis wird über den ContentCacheService angezeigt, der zuerst den L1-Cache im Speicher überprüft, bevor er in der DB nachsieht.

Welche Auswirkungen haben diese "kleinen" Features auf die Kundenbindung?

Favoriten, Zuweisungen und Anhänge sind keine Features, die dazu führen, dass eine App heruntergeladen wird. Niemand sucht im Play Store nach "Aufgaben-App mit Sternanimation". Aber es sind Features, die dazu führen, dass eine App behalten wird.

Laut einer Analyse von AppsFlyer zur mobilen Kundenbindung liegt die durchschnittliche 30-Tage-Kundenbindungsrate für Produktivitäts-Apps bei 4,5 %. Apps, die sich abheben, sind diejenigen, die die Reibung in den täglichen Arbeitsabläufen reduzieren. Eine Aufgabe mit einem Tipp als Favorit markieren, eine Aufgabe einem Kollegen zuweisen, ohne den Kontext zu verlassen, das angehängte Bild sehen, ohne auf einen externen Link klicken zu müssen: Das ist weniger Reibung.

Das ist die Philosophie, die ich bereits im Artikel über kontextbezogene Suche und Wischen verfolgt habe: Jede eingesparte Interaktion ist ein eliminierter Reibungspunkt. Und in der Produktivität ist Reibung der größte Feind der Akzeptanz.

Häufig gestellte Fragen

Können Favoritenaufgaben im Kalender gefiltert werden?

Ja. Der Favoritenfilter ist im Feed und im Kalender verfügbar. Als Favoriten markierte Aufgaben erscheinen mit dem Sternsymbol in allen Ansichten, in denen sie angezeigt werden, einschließlich des Kalenders mit seinen erweiterten Filtern.

Wie viele Personen können einer Aufgabe zugewiesen werden?

Es gibt keine technische Begrenzung. Die Verknüpfungstabelle collaborative.task_assignments erlaubt so viele Zuweisungen wie nötig. In der Praxis wird es schwierig, mehr als 5 Personen derselben Aufgabe zuzuweisen, aber das System unterstützt es.

Gibt es eine Größenbeschränkung für Anhänge?

Ja. Der Free-Plan erlaubt Dateien bis zu 5 MB. Der Pro-Plan erhöht dies auf 25 MB. Der Team-Plan auf 50 MB. Diese Grenzen werden Frontend-seitig vor dem Upload und Backend-seitig über die Supabase Storage-Richtlinien verwaltet.

Was passiert, wenn eine signierte URL während der Anzeige abläuft?

Der StorageService erkennt 403-Fehler und löst automatisch eine Aktualisierung der URL aus. Der Benutzer sieht einen kurzen Lade-Platzhalter, dann erscheint das Bild wieder. Der Vorgang ist transparent.

Werden Favoriten zwischen Mobil und Web synchronisiert?

Ja, in Echtzeit. Der ContentCacheService verwendet die Supabase Realtime-Kanäle, um Favoritenänderungen zwischen allen mit demselben Konto verbundenen Geräten zu verbreiten.