Notez-Workspaces haben schon eine Weile Platz für mehr als eine Person. Typisierte, filterbare Eigenschaften haben Notizen schon länger. Was in der Lücke zwischen diesen beiden Funktionen offensichtlich fehlt, ist eine Zuweisung – eine Notiz, auf der steht: Die hier gehört Marta.
Das ist eine kleine Funktion. Ein Dropdown mit den Leuten im Workspace, eine ID in einer Spalte, ein Chip auf der Karte. Ein Nachmittag Arbeit.
Nicht klein ist die Frage, die niemand stellt, bis es passiert: Marta geht. Was wird aus den vierzig Notizen, auf denen ihr Name steht?
Mitglied ist eine Eigenschaft, kein Feld
Die erste Entscheidung war die langweilige, und sie war mit Absicht langweilig.
Ich hätte Notizen eine Spalte assigneeId geben und eine Auswahl dafür bauen können. Stattdessen wurde member der elfte Eigenschaftstyp, neben Text, Zahl, Datum, Auswahl, Relation und den übrigen.
Der Unterschied zeigt sich überall außer im Code, der den Wert speichert. Weil der Typ Mitglied eine Eigenschaft ist:
Du kannst mehrere davon haben.
AssigneeundReviewersind zwei Eigenschaften, keine Schemaänderung.Sie taucht im Filter-Popover, im Sortiermenü und in der Spaltenauswahl der Tabellenansicht auf, ohne dass eines davon lernen musste, was eine Person ist.
Sie erscheint als Chip auf Karten, in Tabellenzellen und an der Notiz selbst, über denselben Rendering-Pfad wie jede andere Eigenschaft.
Die ganze Funktion ist ein neuer Fall in etwa einem Dutzend switch-Anweisungen, von denen jede sich weigert zu kompilieren, bis der Fall behandelt ist. Die Checkliste hat der Compiler geschrieben.
Das Vorbild gab es schon: relation, der Typ, der auf andere Notizen zeigt. Mehrwertig, geordnet, per Fremdschlüssel an das gebunden, worauf er verweist, und serverseitig aufgelöst, damit Chip und Filter nie unterschiedlicher Meinung darüber sind, was ein Wert bedeutet. Eine Mitglied-Eigenschaft ist eine Relation-Eigenschaft, deren Ziel eine Person ist statt einer Notiz. Ich habe sie fast Zeile für Zeile übernommen.
Wo die ID liegt
Ein Mitglied-Wert ist eine User-ID. Es gab eine verlockende Abkürzung: Eigenschaftswerte haben schon eine Spalte valueText, und eine User-ID ist Text. Dort speichern, einen Tag früher ausliefern.
Stattdessen habe ich der ID eine eigene Spalte mit einem echten Fremdschlüssel gegeben, aus einem Grund, der nichts mit Ordnungsliebe zu tun hat.
Konten werden gelöscht. Wenn jemand sein Notez-Konto löscht, verschwindet diese Zeile, und jeder Verweis darauf muss mit ihr verschwinden. Sonst bleibt ein Wert übrig, der auf einen User zeigt, den es nicht mehr gibt, und jeder Lesezugriff muss vorsorglich Geister herausfiltern. Mit einem Fremdschlüssel und on delete cascade erledigt die Datenbank das selbst, korrekt und für immer, mit einer einzigen Zeile im Schema. Mit valueText hätte ich das selbst übernehmen müssen: ein manuelles Aufräumen im Hook für gelöschte Konten, dazu ein Filter für verwaiste IDs auf jedem Lesepfad, dazu der Bug, den ich irgendwann ausliefere, wenn jemand einen zwölften Lesepfad hinzufügt und es vergisst.
Der Check-Constraint, nach dem genau eine Payload-Spalte nicht null ist, ist von fünf Spalten auf sechs gewachsen. Das sind die gesamten Kosten.
Die eigentliche Frage
Jetzt zum interessanten Teil. Marta wird aus dem Workspace entfernt oder geht selbst. Ihr Name steht auf vierzig Notizen. Es gibt genau drei Möglichkeiten:
Das Entfernen blockieren. Niemanden entfernen, bevor seine Zuweisungen gelöscht sind. Das ist die schlechteste Option, und man sieht in etwa zehn Sekunden, warum: Marta kann einen Workspace, aus dem sie raus will, erst verlassen, wenn ein Eigentümer vierzig Notizen von Hand bearbeitet hat. Ein Sicherheitsmechanismus, der eine Person als Geisel nimmt, ist kein Sicherheitsmechanismus.
Den Wert behalten und als tot markieren. Die Zeile behalten, den Namen trotzdem auflösen und ausgegraut anzeigen: ein Tombstone, ein Grabstein für den Wert. So mache ich es bei Relation-Werten, die auf Notizen im Papierkorb zeigen. Es gab also ein Vorbild, und das war mein erster Plan.
Die Werte mit der Mitgliedschaft löschen. Wer entfernt wird, verliert seine Zuweisungen in derselben Transaktion.
Ich habe mich für die dritte entschieden und den Tombstone-Plan verworfen, während ich ihn aufschrieb.
Warum Tombstones hier verlieren
Für eine Notiz im Papierkorb sind Tombstones richtig. Die Notiz existiert noch, im Papierkorb, dreißig Tage lang wiederherstellbar. Der Tombstone zeigt auf etwas, und er sagt dir die Wahrheit: Dieses Ziel liegt im Papierkorb.
Ein ausgeschiedenes Mitglied liegt in keinem Papierkorb. Es gibt nichts wiederherzustellen. Und zwei konkrete Dinge gehen schief:
Niemand findet diese Notizen je wieder. Der Mitglied-Filter listet die Leute, die gerade im Workspace sind. Genau das macht ihn zu einer brauchbaren Auswahl statt zu einer ständig wachsenden Liste aller, die den Workspace je angefasst haben. Marta steht also nicht darin. Vierzig Notizen tragen jetzt einen Wert, nach dem kein Filter auswählen kann. Finden kann man sie nicht, nur zufällig über sie stolpern.
Die Notizen wehren sich still gegen jede Bearbeitung. Jedes Speichern einer mehrwertigen Eigenschaft schickt das ganze Array. Die Validierung beim Schreiben prüft, ob jede ID zu einem aktuellen Mitglied gehört. Eine Notiz, die Marta und jemandem zugewiesen ist, der noch da ist, würde also das nächste Speichern ablehnen: Du müsstest Martas toten Chip bemerken und entfernen, bevor du jemanden hinzufügen kannst. Ich könnte eine Ausnahme für IDs einbauen, die schon gespeichert sind, und genau so eine subtile Regel wird zwei Funktionen später zum Bug.
Löschen hat echte Kosten, und die sollte man klar aussprechen: Wer erneut eingeladen wird, bekommt seine Zuweisungen nicht zurück. Diese Information ist wirklich verloren. Ich halte das für den besseren Tausch, denn die Alternative ist Information, die vorhanden, aber unerreichbar ist, und das ist schlimmer als gar keine: Sie sieht aus wie Daten und verhält sich wie Müll.
Das Vorbild fürs Löschen lag dann direkt in derselben Datei. Wird eine Option einer Auswahl gelöscht, verschwinden ihre Werte schon jetzt per Kaskade von jeder Notiz. Der Bestätigungsdialog zum Entfernen eines Mitglieds sagt bereits: Die Person verliert den Zugriff auf alle Notizen in diesem Workspace. Die Zuweisung zu löschen passt zu beidem.
Der Fall, der durchrutscht
Es gibt ein viertes Szenario, das die Transaktion nicht abdeckt, und dazu will ich ehrlich sein.
Ein Mitglied, das zwischen der Validierung und dem Insert entfernt wird, in einem Fenster von Millisekunden, hinterlässt eine Zeile, die das Aufräumen nie gesehen hat. Den User gibt es noch, also wird der Chip weiter angezeigt. Es ist ein veralteter Wert, und jede weitere Bearbeitung dieser Eigenschaft wird abgelehnt, bis du ihn abwählst.
Ich könnte das mit einem Lock schließen. Ich habe mich dagegen entschieden: Das Fenster ist Millisekunden lang, der Fehler ist ein verwirrender Chip und kein Datenverlust, und weil die Auswahl ausgewählte Einträge oben angeheftet lässt, kannst du ihn immer abwählen. Manche Race Conditions sind einen Kommentar wert statt eines Locks.
Was bewusst fehlt
Zuweisen aus dem KI-Chat. Als die Funktion erschien, konnte der Chat lesen, wer einer Notiz zugewiesen ist, aber niemanden zuweisen: Kein Tool gab ihm die Liste der Leute in deinem Workspace, er konnte eine ID also nur raten, und ein plausibel aussehender Rateversuch ist die schlimmste denkbare Fehlerart für einen Schreibvorgang. Inzwischen bekommt der Chat jedes Mitglied des Workspace mit seiner ID und setzt eine Mitglied-Eigenschaft wie jeden anderen Wert.
Boards, nach Zuständigen gruppiert. Das Board hängt an seiner eigenen Spalten-Eigenschaft. Nach einer beliebigen Eigenschaft zu gruppieren ist eine größere Änderung als diese hier.
„Mir zugewiesen.“ Ein Filter auf den eigenen Namen mit einem Klick war der naheliegende nächste Schritt, und er ist inzwischen da: Schalte In der Seitenleiste anzeigen für eine Mitglied-Eigenschaft ein, und die Seitenleiste listet alle im Workspace mit der Zahl ihrer Notizen. Ein Klick auf deinen Namen filtert auf deine.
Benachrichtigungen. Wenn dir etwas zugewiesen wird, erfährst du es nicht. Sag deinen Kollegen Bescheid.
Als dieser Text entstand, waren zwei dieser vier schwer und zwei einfach noch nicht gebaut. Das Zuweisen aus dem Chat und der Filter sind seitdem erschienen, Boards nach Zuständigen und Benachrichtigungen nicht. Der Unterschied ist wichtig, und ich sage lieber, was was ist, als eine Funktionsliste so klingen zu lassen, als stünde alles kurz bevor.
Was ich daraus mitnehme
Ein Feld hinzuzufügen ist leicht. Zu einer Funktion wird ein Feld erst, wenn entschieden ist, was es an den Tagen tut, an denen nicht alles glattläuft: wenn die Person geht, wenn das Konto gelöscht wird, wenn zwei Schreibvorgänge um die Wette laufen.
Für die Auswahl und den Chip habe ich vielleicht ein Fünftel der Zeit gebraucht. Der größte Teil floss in den Absatz im Hilfeartikel, der erklärt, was passiert, wenn jemand geht. Erst als ich diesen Satz aufschrieb, zeigte sich, dass der erste Plan falsch war.
Weiterlesen
Teilen ohne Merge-Konflikt – warum ein gemeinsamer Workspace den zweiten von zwei widersprüchlichen Speichervorgängen ablehnt, statt beide zusammenzuführen.
Zwei Assistenten, dieselben Tools – wie KI-Chat und MCP-Server bei sechzehn und zehn Tools auseinanderliefen, und der Bug im Notizbaum, der beim Angleichen auftauchte.
Notez: eine Notizen-App für Entwickler – was Notez ist, auf welchen Wegen eine Notiz hineinkommt und was jeder Plan enthält.