From e8e675fd0734e0ae5faf5881613239bf9d0ffe09 Mon Sep 17 00:00:00 2001 From: Maximilian Stubhan Date: Thu, 10 Sep 2026 11:36:38 +0200 Subject: [PATCH] Turn the note filter around: yours by default, colleagues added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yesterday's version had it the other way — everyone visible, untick to hide. The decision from the business side is the opposite: you see your own notes, and you tick the colleagues you also want. So note_mutes becomes note_subscriptions and the predicate flips from `not exists` to `exists`. The existing rows are not carried over. The meaning inverts rather than the sign: converting faithfully ("everyone except the muted") would write almost the whole roster into the new table and reproduce exactly the state the change is meant to end. Anyone opening the setting tomorrow would think it had not taken effect. The table is a day old; what is lost is a few ticks from trying it out. What this costs is worth saying plainly: the silent case that could not happen under exceptions can happen now. Do not tick a colleague and you will not see her follow-ups — not while she is on holiday either. That is the flip side of the decision, and it is written down in the migration rather than discovered later. Each note now says who wrote it. Own notes read "von mir" rather than repeating your own name, which would sit on every second line and tell nobody anything. The flag is computed on the server: the user id is already there, and threading it through four components for one word is a poor trade. The counter on the button follows the same turn — "+2" for what you added, nothing when you added nothing. Verified: 21 tests, five mutation-checked (restoring `not exists`, dropping the own-notes clause, inverting the default, hiding the author, and printing your own name instead of "von mir" each turn them red). 489 tests, typecheck, lint, schema drift and build clean. The migration is reviewed but not run — no reachable database here. Co-Authored-By: Claude Opus 5 --- actions/notes.ts | 32 +++---- components/shell/NotesBell.tsx | 17 +++- ...60910100000_notiz_abos_statt_ausnahmen.sql | 91 +++++++++++++++++++ lib/notes.ts | 59 +++++++----- lib/shell-data.ts | 20 ++-- lib/types.ts | 14 ++- tests/components/NotesBell.test.tsx | 33 ++++++- tests/unit/notes-visibility.test.ts | 31 ++++--- 8 files changed, 217 insertions(+), 80 deletions(-) create mode 100644 db/migrations/20260910100000_notiz_abos_statt_ausnahmen.sql diff --git a/actions/notes.ts b/actions/notes.ts index 3eaf974..b474824 100644 --- a/actions/notes.ts +++ b/actions/notes.ts @@ -6,7 +6,7 @@ import { withUser } from "@/lib/db"; import type { ActionResult } from "@/lib/db/rpc"; /** - * Notizen einer Kollegin oder eines Kollegen ein- oder ausblenden. + * Notizen einer Kollegin oder eines Kollegen hinzuwählen oder abwählen. * * Kein Aufruf einer SQL-Funktion und kein Protokolleintrag, anders als bei * allem, was Personaldaten ändert: das hier ist eine persönliche @@ -15,7 +15,7 @@ import type { ActionResult } from "@/lib/db/rpc"; * Dasselbe Muster wie bei gespeicherten Auswertungen und Entwürfen * (actions/reports.ts, actions/hireDrafts.ts). * - * Abgesichert ist es trotzdem: die Regel `note_mutes_owner` lässt nur Zeilen + * Abgesichert ist es trotzdem: die Regel `note_subscriptions_owner` lässt nur Zeilen * zu, deren `user_id` die angemeldete Person ist. Eine fremde Einstellung * liesse sich auch mit erfundenen Werten nicht schreiben. */ @@ -25,28 +25,28 @@ export async function setNotizSichtbarkeit(payload: { }): Promise { const userId = await requireUserId(); - // Die eigenen Notizen bleiben immer sichtbar. Die Prüfbedingung der - // Tabelle weist das ohnehin ab; hier kommt die Meldung heraus, die jemand - // lesen kann, statt einer Verletzungsmeldung aus der Datenbank. + // Die eigenen Notizen sind ohnehin immer dabei. Die Prüfbedingung der + // Tabelle weist das ab; hier kommt die Meldung heraus, die jemand lesen + // kann, statt einer Verletzungsmeldung aus der Datenbank. if (payload.kollegeId === userId) { - return { success: false, error: "Die eigenen Notizen lassen sich nicht ausblenden." }; + return { success: false, error: "Die eigenen Notizen sind immer dabei." }; } try { await withUser(userId, async (tx) => { if (payload.sichtbar) { - await tx - .deleteFrom("note_mutes") - .where("user_id", "=", userId) - .where("muted_user_id", "=", payload.kollegeId) - .execute(); - } else { - // `on conflict do nothing`: zweimal dasselbe Ausblenden ist kein + // `on conflict do nothing`: zweimal dasselbe Hinzuwählen ist kein // Fehler, sondern derselbe Wunsch — etwa wenn zwei Reiter offen sind. await tx - .insertInto("note_mutes") - .values({ user_id: userId, muted_user_id: payload.kollegeId }) - .onConflict((oc) => oc.columns(["user_id", "muted_user_id"]).doNothing()) + .insertInto("note_subscriptions") + .values({ user_id: userId, author_user_id: payload.kollegeId }) + .onConflict((oc) => oc.columns(["user_id", "author_user_id"]).doNothing()) + .execute(); + } else { + await tx + .deleteFrom("note_subscriptions") + .where("user_id", "=", userId) + .where("author_user_id", "=", payload.kollegeId) .execute(); } }); diff --git a/components/shell/NotesBell.tsx b/components/shell/NotesBell.tsx index 7b11206..71bd072 100644 --- a/components/shell/NotesBell.tsx +++ b/components/shell/NotesBell.tsx @@ -28,7 +28,7 @@ export function NotesBell({ notes, kollegen }: { notes: OpenNote[]; kollegen: Ko const containerRef = useRef(null); const sichtbarkeit = (k: Kollege) => eigen[k.id] ?? k.sichtbar; - const ausgeblendet = kollegen.filter((k) => !sichtbarkeit(k)).length; + const dazugewaehlt = kollegen.filter((k) => sichtbarkeit(k)).length; async function schalte(k: Kollege) { const neu = !sichtbarkeit(k); @@ -91,16 +91,16 @@ export function NotesBell({ notes, kollegen }: { notes: OpenNote[]; kollegen: Ko > Kolleg:innen - {/* Nur wenn eingeschränkt: sonst ist die Zahl bei jedem - Aufschlagen zu lesen, ohne je etwas zu bedeuten. */} - {ausgeblendet > 0 && ({ausgeblendet} aus)} + {/* Nur wenn jemand dazugewählt ist: eine „0" bei jedem + Aufschlagen zu lesen, sagt nichts. */} + {dazugewaehlt > 0 && +{dazugewaehlt}} {zeigeEinstellung && (

- Wessen Notizen hier erscheinen. Die eigenen sind immer dabei. + Wessen Notizen hier zusätzlich erscheinen. Die eigenen sind immer dabei.

{kollegen.length === 0 ? (

Keine weiteren HR-Kolleg:innen freigeschaltet.

@@ -138,6 +138,13 @@ export function NotesBell({ notes, kollegen }: { notes: OpenNote[]; kollegen: Ko {fmtDate(n.created_at)}

{n.note_text}

+ {/* Von wem die Notiz stammt. Sichtbar sind die eigenen und + die hinzugewählten — ohne diese Zeile wäre nicht zu + unterscheiden, welche davon man gerade liest. „Ich" + statt des eigenen Namens: den kennt man. */} +

+ {n.vonMir ? "von mir" : `von ${n.author_name}`} +

{n.due_date &&

🔔 fällig {fmtDate(n.due_date)}

}