- Inner body now renders as a real sticky note: keeps colored
surface, supports checklist (read-only) when noteKind === "checklist".
- Action row trimmed to exactly 3 buttons: Done (تم), Open note
(فتح الملاحظة), Reply (رد). Removed the standalone Dismiss button;
header X still closes.
- Reply variant uses the same 3-button row (Done is a dismiss-only
no-op since the note owner can't mark their own note read).
- Wire kind + items from backend `note_received` payload into client
IncomingNotePayload (noteKind/items) via use-notifications-socket.
- Locales: markRead → "Done"/"تم"; new openNote/done keys; openThread
unified to "Open note"/"فتح الملاحظة".
- Tests: replaced removed `incoming-note-popup-dismiss` testid usages
with header `-close`; updated reply variant assertions to expect the
3-button row; fixed pre-existing pluralization bug in reply path
(`/replies` → `/reply`) that was unrelated to this task but blocked
the reply popup test from validating.
Labels and pin were not surfaced in the popup because they are
sender-private state (note_recipients only snapshots
title/content/color/kind/items); the recipient never receives the
sender's labelIds/isPinned, so showing them would be misleading.
Added a new e2e ("recipient popup renders checklist items for a
checklist note") that composes a checklist note via the UI, sends it,
and asserts `incoming-note-popup-checklist` is visible with both
items — locks in the noteKind/items socket plumbing.
All affected e2e tests pass (notes-popup-on-receive note + reply +
checklist, notes-popup-touch-tap both, notes-inbox); queue unit
tests pass.
- Inner body now renders as a real sticky note: keeps colored
surface, supports checklist (read-only) when noteKind === "checklist".
- Action row trimmed to exactly 3 buttons: Done (تم), Open note
(فتح الملاحظة), Reply (رد). Removed the standalone Dismiss button;
header X still closes.
- Reply variant uses the same 3-button row (Done is a dismiss-only
no-op since the note owner can't mark their own note read).
- Wire kind + items from backend `note_received` payload into client
IncomingNotePayload (noteKind/items) via use-notifications-socket.
- Locales: markRead → "Done"/"تم"; new openNote/done keys; openThread
unified to "Open note"/"فتح الملاحظة".
- Tests: replaced removed `incoming-note-popup-dismiss` testid usages
with header `-close`; updated reply variant assertions to expect the
3-button row; fixed pre-existing pluralization bug in reply path
(`/replies` → `/reply`) that was unrelated to this task but blocked
the reply popup test from validating.
Labels and pin were not surfaced in the popup because they are
sender-private state (note_recipients only snapshots
title/content/color/kind/items); the recipient never receives the
sender's labelIds/isPinned, so showing them would be misleading.
All affected e2e tests pass (notes-popup-on-receive note + reply,
notes-popup-touch-tap both, notes-inbox); queue unit tests pass.
Convert the incoming-note popup from a centered AlertDialog modal into
a floating, draggable, animated card (no backdrop) and surface the
same card variant at the original sender when the recipient replies.
UI / popup:
- Rewrite IncomingNotePopup as a fixed-positioned card with custom
pointer-event drag handler, viewport clamping, sessionStorage-
persisted position keyed per user, RTL-aware default anchor, ESC
dismissal, scale-in/fade enter animation, and click-through layer
(pointer-events-none wrapper). Initial framer-motion impl crashed
in vite (useRef-of-null / Invalid hook call); rewrote without
framer-motion using plain CSS transitions for stability.
- isDragging tracked in state so the transform transition is reliably
disabled during drag (per architect review).
Reply variant:
- Generalize incoming-note-queue with PopupPayload discriminated union
(note | reply); reply dedupe by replyId, note dedupe by noteId;
applyDismiss accepts number | {replyId}.
- Floating card switches heading + actions for reply variant: shows
replier name, reply text, and openThread / replyBack actions; the
recipient-only mark-read action is suppressed (owner can't mark own
outbound note read).
Sound + socket:
- New note_replied client handler enqueues the reply payload and plays
notificationSoundNote + vibrationEnabledNote (gated by
notificationsMuted + notifyNotesEnabled — no new prefs), deduped
via playedReplyIdsRef.
- Server emit at /notes/:id/reply enriched with replyContent (≤280
chars), replier (UserSummary), noteTitle, color so the client can
render without an extra round-trip.
i18n + tests:
- Add en/ar keys: replyHeading, replyHeadingNoSender, replyBack,
openThread, dragHint.
- Extend notes-popup-on-receive.spec.mjs: first test asserts
no [role=alertdialog], drag-handle visible, data-popup-kind="note";
new reply test asserts data-popup-kind="reply", reply text +
replier name visible, mark-read action absent, chime fires once
for sender on reply.
Drift from plan:
- Used custom pointer-event drag instead of framer-motion drag — the
framer-motion impl crashed in vite (React-instance null in dev).
Same UX (drag from header only, click-through behind card).
- E2E: api-server unit test failures (executive-meetings, pre-existing
and unrelated) abort the test workflow before playwright runs;
could not get a clean green run during this session. Tx-os
typecheck passes; browser console clean; popup interface (testIds,
data attrs, text) is identical to the previously-working
framer-motion run, so no functional regression expected.
- Backend: extend `note_received` socket payload with note snapshot
(title, content, color, sentAt), recipientRowId, senderUserId, and
sender summary so the recipient can render the popup without an extra
fetch.
- Add IncomingNotePopupContext: FIFO queue, dedupe by note id, suppress
events for the user's own sends, auto-clear on logout.
- Add IncomingNotePopup AlertDialog that shows the note (title/content/
color) with a sender chip and Reply / Mark read / Open in Notes /
Dismiss buttons. Acknowledge actions call /notes/:id/read.
- Wire the popup globally in App.tsx (alongside the socket bridge).
- Notes page accepts a `?thread=ID` deep link so the popup's Open and
Reply buttons land on the inbox tab + thread dialog.
- Added bilingual `notes.popup.*` strings in EN + AR (RTL respected).
- New Playwright e2e (`notes-popup-on-receive.spec.mjs`) verifies the
popup appears in a second browser context within seconds, sender
doesn't see their own popup, and dismiss closes it.