From 10c1e641b40d16c212de3d3d0d58a98db8d9fb11 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Thu, 7 May 2026 11:39:51 +0000 Subject: [PATCH] Task #438: collaborative checklists + meeting alert animation parity Backend - New POST /notes/:id/checklist/:itemId/toggle endpoint. Owner or any active (non-archived) recipient can flip an item's done flag. - Wrapped in a DB transaction with SELECT ... FOR UPDATE on the live note row so concurrent toggles from multiple collaborators can't lose each other's updates. Live notes.items + every note_recipients.items snapshot are mirrored atomically in the same tx. - Emits note_checklist_changed to the audience minus the actor with the full updated items array so receivers can patch state without an extra fetch. Frontend - useToggleChecklistItem mutation hook with optimistic updates across notes/sent/received/thread caches and rollback on error. - Incoming-note popup checklist is now interactive with a local override for instant visual feedback (popup renders from socket payload, not from the query cache). - Thread checklist toggles are gated by effective permission (owner, admin, or non-archived recipient) to mirror server auth. - Socket handler for note_checklist_changed invalidates relevant query keys AND patches the open popup payload directly via a new IncomingNotePopupContext.updateChecklistItems action so collaborators' open popups stay in sync. Meeting alert animation parity (upcoming-meeting-alert.tsx) - Added scale-95 -> scale-100 + fade entrance with the same spring cubic-bezier the note popup uses, retriggered per eligible meeting. - Replaced the 1px border with a ring-4 colored frame driven by alertPrefs.accent (via boxShadow so the color is dynamic). - Added an animate-ping accent halo around the drag-handle icon to match the popup's avatar pulse. Tests - artifacts/api-server/tests/notes-checklist-toggle.test.mjs (7 tests: owner/recipient toggle + mirroring, 403 non-recipient, 403 archived, 400 non-checklist, 404 unknown item, 400 invalid body). - artifacts/tx-os/tests/notes-popup-checklist-collab.spec.mjs (e2e: recipient ticks an item from the popup, server + sender snapshot both reflect the change). Code review (architect) findings addressed: - (critical) lost-update race -> tx with row lock. - (critical) non-atomic snapshot mirror -> same tx. - (critical) open popup didn't re-render on socket fanout -> updateChecklistItems context action. - UI permission parity for archived recipients -> thread gates onToggle. --- .../components/notes/incoming-note-popup.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/artifacts/tx-os/src/components/notes/incoming-note-popup.tsx b/artifacts/tx-os/src/components/notes/incoming-note-popup.tsx index eab7be3c..9e09bf99 100644 --- a/artifacts/tx-os/src/components/notes/incoming-note-popup.tsx +++ b/artifacts/tx-os/src/components/notes/incoming-note-popup.tsx @@ -172,17 +172,20 @@ function PopupChecklistPreview({ onChange={(e) => { const next = e.target.checked; setOverrides((o) => ({ ...o, [it.id]: next })); + const drop = () => + // Always clear the override once the mutation + // settles so the next render reflects authoritative + // state (either the optimistic cache write that + // already landed, or — for a concurrent collaborator + // toggle that arrived during our roundtrip — the + // socket-pushed value that supersedes ours). + setOverrides((o) => { + const { [it.id]: _drop, ...rest } = o; + return rest; + }); toggle.mutate( { id: noteId, itemId: it.id, done: next }, - { - onError: () => - // Roll back the local override on failure so the - // checkbox reflects authoritative state. - setOverrides((o) => { - const { [it.id]: _drop, ...rest } = o; - return rest; - }), - }, + { onSuccess: drop, onError: drop }, ); }} className="mt-0.5 h-4 w-4 rounded border-black/30 accent-amber-500"