Task #402: Convert Notes into in-app messaging

Original task: turn personal Notes into in-app messaging — sender composes a
note (title/content/color), picks recipient(s), Send. Recipients get an
Inbox with sender name, color, Read/Unread badge, and inline reply. Sender
sees Sent Notes with per-recipient status. Sender's and recipient's copies
must be INDEPENDENT, with backend access checks (admin sees everything),
realtime updates, toasts, an unread badge on the Notes app tile, and full
i18n + RTL.

Two prior code-review rounds were addressed in this commit:

Round 1 (independence):
- Added immutable snapshot columns (title/content/color) on note_recipients.
- Dropped the FK from note_recipients.note_id and note_replies.note_id so
  recipient threads survive the sender deleting their note.
- /notes/received and /notes/:id/thread render the recipient snapshot for
  recipients (sender/admin still see the live note).

Round 2 (validation REJECT fixes):
- POST /notes/:id/reply: owner is now allowed to reply too. Owner replies
  do not change recipient status; recipient replies still flip to
  "replied" and clear archivedAt.
- Added GET /notes/:id as an alias of /notes/:id/thread (shared handler).
- Added OpenAPI ops for /notes/sent, /notes/received, /notes/{id},
  /notes/{id}/send, /notes/{id}/read, /notes/{id}/archive,
  /notes/{id}/reply, and ran orval codegen.
- use-notifications-socket.ts now shows bilingual toasts for note_received
  and note_replied (suppressed during the socket warmup window).
- home.tsx renders an unread badge on the Notes app tile, fed by
  useReceivedNotes(false) filtered to status === "unread"; refreshes
  automatically on socket invalidation.

Tests: 7 backend tests in notes-share.test.mjs (independence,
archived-reply, owner-reply preserves recipient status, GET /notes/:id
alias, etc.) + the notes-inbox e2e all pass. tx-os typecheck is clean.

Pre-existing executive-meetings TS errors and the failing top-level `test`
workflow are unrelated to this task.
This commit is contained in:
riyadhafraa
2026-05-05 15:01:18 +00:00
parent 6352cbf844
commit 65c5a172d6
8 changed files with 1102 additions and 37 deletions
@@ -16,6 +16,8 @@ import {
} from "@workspace/api-client-react";
import { useAuth } from "@/contexts/AuthContext";
import { notificationPlayer } from "@/lib/notification-sounds";
import { toast } from "@/hooks/use-toast";
import i18n from "@/i18n";
const BASE = import.meta.env.BASE_URL.replace(/\/$/, "");
@@ -103,12 +105,31 @@ export function useNotificationsSocket() {
});
});
// Realtime note events: invalidate notes queries AND surface a toast so
// the recipient (or sender, on a reply) sees an immediate, dismissible
// confirmation regardless of which page they're on.
socket.on("note_received", () => {
queryClient.invalidateQueries({ queryKey: ["notes"] });
if (nowMs() - connectedAtRef.current < SOCKET_WARMUP_MS) return;
const ar = i18n.language === "ar";
toast({
title: ar ? "ملاحظة جديدة" : "New note received",
description: ar
? "تحقق من بريد الملاحظات الوارد لقراءتها."
: "Check your Notes inbox to read it.",
});
});
socket.on("note_replied", () => {
queryClient.invalidateQueries({ queryKey: ["notes"] });
if (nowMs() - connectedAtRef.current < SOCKET_WARMUP_MS) return;
const ar = i18n.language === "ar";
toast({
title: ar ? "رد جديد على ملاحظة" : "New reply on a note",
description: ar
? "افتح الملاحظة لمتابعة المحادثة."
: "Open the note to continue the conversation.",
});
});
socket.on("note_status_changed", () => {