Commit Graph

910 Commits

Author SHA1 Message Date
riyadhafraa 4dd28a821c Task #448: remove gap under Notes header by switching back to sticky
Task #446 made the Notes header `position: fixed` and added an
aria-hidden spacer below it sized to the header's measured height
(via ResizeObserver). The spacer was exactly the "big empty band"
the user kept seeing between the header bar and the
"اكتب ملاحظة..." composer — the page background gradient showed
through the reserved space.

Switched the header back to `sticky top-0 z-20`. Sticky keeps it
pinned to the top of the viewport while the page scrolls, but it
stays in document flow, so the composer renders immediately under
the bar with no artificial gap. Removed the now-unused
`headerRef`, `headerHeight` state, ResizeObserver effect, and the
spacer div. Bumped composer wrapper padding from `pt-1` back to
`pt-3` for a small breathing space (no longer competing with the
spacer).

No other changes; folders rail, composer behavior, RTL/LTR all
untouched.
2026-05-10 07:33:30 +00:00
riyadhafraa fa3b56be4c Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ba4b147a-8cb8-4bf0-a293-a3d2ab846c97
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/dsH5vgE
Replit-Helium-Checkpoint-Created: true
2026-05-10 07:33:02 +00:00
riyadhafraa 32cda2d86b notes: tighten gap above top composer (Task #447)
The composer wrapper that sits directly under the fixed header's
height-matching spacer had `pt-4` (16px) on top, which made an obvious
empty band between the bottom border of the frozen header and the top
of the "اكتب ملاحظة..." card. Reduced that to `pt-1` (4px) so the
composer now sits flush under the bar with a minimal breathing space,
without affecting the rest of the page (folders rail / browser keep
their own `py-6`).

artifacts/tx-os/src/pages/notes.tsx:497 — single class change.
2026-05-10 07:28:37 +00:00
riyadhafraa 0dbec0a6ee Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4bd17917-6e2b-4763-a616-d6330b5c77f3
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/dsH5vgE
Replit-Helium-Checkpoint-Created: true
2026-05-10 07:28:22 +00:00
riyadhafraa c3999b9af7 notes: freeze header + move composer to top of page (Task #446)
User wanted the Notes top bar to truly stay pinned (فريز / ثابت) while
scrolling, and wanted the "اكتب ملاحظة..." composer to live at the top
of the page above the folders area instead of being tucked under the
folders chips and only showing inside a folder.

Changes (artifacts/tx-os/src/pages/notes.tsx):
- Header bar switched from `sticky top-0` to `fixed top-0 inset-x-0
  z-30`. Measured its live height with a `ResizeObserver` + `offsetHeight`
  (header wraps differently per tab / label set / viewport width), and
  rendered a same-height aria-hidden spacer immediately below so the
  first row of content never slides under the bar.
- Composer was lifted out of the in-folder branch and is now rendered as
  the first content block under the spacer (above FoldersRail and
  FoldersBrowser) on the active "My Notes" view. It is hidden on Inbox,
  Sent, Archived, and the Shared-with-me read-only folder view, so the
  read-only contract from Task #445 stays intact. `folderSelection` is
  still passed in unchanged, so a new note created from this top
  composer continues to land in the currently-selected folder (or
  Unfiled if none).
- Removed the duplicate Composer mount inside the per-folder branch and
  left a comment explaining the move.

Notes:
- No backend or i18n changes — composer placeholder, search input, tabs,
  label chips and grid/list toggle all kept their existing markup and
  behavior.
- Pre-existing TS errors in artifacts/api-server/src/routes/executive-meetings.ts
  remain (out of scope) and the `test` workflow continues to fail for
  that unrelated reason.
2026-05-10 07:26:05 +00:00
riyadhafraa 4f4400b486 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: fd43212b-7227-4948-a4dd-ca5e54e39291
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-10 07:23:51 +00:00
riyadhafraa 7384711705 notes: address review feedback for folder sharing (Task #445)
Re-applied review fixes on top of the live folder-sharing implementation:

- PATCH/DELETE /notes/:id now do an id-only lookup and return an explicit
  403 when the caller is not the owner (previously returned an ambiguous
  404). This makes the read-only contract for shared-folder recipients
  precise instead of silently masquerading as "not found".
- PUT /note-folders/:id/shares rejects self-share with 400
  "Cannot share folder with yourself" instead of silently filtering the
  caller's id out of the recipient set.
- GET /note-folders/shared-with-me now includes a `noteCount` (live count
  of the owner's non-archived notes in the folder) computed via SQL
  subquery; SharedFolder TS type updated; folders rail renders the count
  next to each shared folder.
- SharedFolderView accepts the page's `search` value and filters the
  read-only list client-side over title + content + checklist item text,
  matching the owner's search experience. Empty state distinguishes
  "no notes" from "no matches".
- Added GET /notes?sharedFolderId=:id as a spec-aligned alias that
  returns the owner's live notes inside a folder shared with the caller
  (gated by an active row in note_folder_shares).
- Updated the stale comment block above the share routes that claimed
  recipient writes "just 404"; documents the new 403 contract.

Deviations / out of scope:
- Project uses `drizzle-kit push` (no migrations directory); no SQL
  migration file was added.
- Pre-existing TS errors in artifacts/api-server/src/routes/executive-meetings.ts
  remain; unrelated to Task #445.
- "Strict reject for nonexistent recipient ids in share PUT" left as
  optional follow-up (currently silently dropped per existing behavior).
2026-05-09 11:13:56 +00:00
riyadhafraa a4949983f3 Notes: live folder-sharing (read-only) — Task #445
Owner can share a whole folder with other users; recipients see it
under "Shared with me" in the folders rail and view notes read-only
(cannot edit, add, move, or delete).

- DB: new noteFolderSharesTable (folderId/recipientUserId, cascade,
  unique idx). Pushed via drizzle-kit.
- API (artifacts/api-server/src/routes/notes.ts):
  - GET/PATCH/POST /note-folders return sharedWithCount.
  - GET /note-folders/shared-with-me, GET /note-folders/:id/shares,
    PUT /note-folders/:id/shares (idempotent diff), DELETE
    /note-folders/:id/shares/:userId, GET /note-folders/:id/shared-notes
    (verifies recipient via noteFolderSharesTable, stamps readOnly).
  - Emits note-folder-shared / note-folder-unshared on share changes.
  - Existing note write paths remain owner-scoped → recipients can't
    mutate owner notes.
- Frontend types/hooks (notes-api.ts): SharedFolder, FolderShareRecipient,
  SharedFolderNote/View; useFolderShares, useUpdateFolderShares,
  useSharedWithMeFolders, useSharedFolderNotes.
- FoldersRail: Share menu item, sharedWithCount badge, "Shared with me"
  section, "shared-folder" selection kind, onShareFolder prop.
- notes.tsx: SharedFolderView (no Composer, read-only cards using
  ChecklistView), FolderShareDialog (seeds existing recipients,
  idempotent PUT on save), revocation fallback effect, dialog mount.
- use-notifications-socket.ts: subscribe to note-folder-shared /
  -unshared → invalidate shared-with-me + open shared-folder notes for
  live rail/view refresh.
- i18n: AR/EN keys for share/sharedBy/sharedByName/sharedWithCount/
  sharedWithMe/readOnly/shareRevoked/sharedEmpty/shareSaved/shareFailed.

Pre-existing failing `test` workflow (executive-meetings type errors)
is unrelated and out of scope.
2026-05-09 11:07:30 +00:00
riyadhafraa e287278819 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 5033203a-6f1e-4742-926f-c2d621875fb1
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-09 10:54:00 +00:00
riyadhafraa 5ef0250195 Task #444: Add Send button to note composer
Problem: the new-note composer in the Notes page only exposed Save —
to send a freshly-typed note the user had to save it, hover the
resulting card, and click the small Send icon. The user asked for an
explicit Send action right inside the composer and confirmed they're
fine with auto-saving as a side effect of pressing Send.

Implementation (artifacts/tx-os/src/pages/notes.tsx):
- Added `onSend: (id: number) => void` prop to <Composer> and wired
  the NotesPage call site to forward to the existing
  `setSendForNoteId(id)` flow that already mounts <SendNoteDialog>.
- Extracted a `buildPayload()` helper from save() so save() and the
  new sendNew() use the exact same payload shape (title, content,
  color, labels, kind, items, folderId).
- Added `sendNew()` that runs the same `create.mutate` save and, on
  success, resets the composer and calls `onSend(created.id)` so the
  send dialog opens for the freshly created note. On failure the
  composer state is left intact (same UX as save() failures).
- Added a Send icon button next to Save inside the composer footer,
  aria-labeled with the existing `notes.send` translation and tagged
  `data-testid="notes-composer-send"`. The button is disabled while
  the composer is empty (no title and no content/checklist text) and
  while a create request is in flight to prevent duplicate notes
  from rapid clicks.

The button lives inside the composer's `ref` so it doesn't trip the
existing click-outside-to-save handler, and <SendNoteDialog> renders
in a Radix portal which is already excluded from that handler.

No changes to send dialog logic, the per-card hover Send button, or
any other surface. tsc --noEmit passes for tx-os. The pre-existing
api-server `test` workflow failures (executive-meetings, service-
orders) are unrelated and out of scope.
2026-05-09 10:42:11 +00:00
riyadhafraa efe349ce8f Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1b3afbbd-19b0-40f5-a710-334c770eba5f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-09 10:40:30 +00:00
riyadhafraa 2a89b38b04 Task #443: Fix notes search icon overlapping placeholder in Arabic
Problem: in the Notes page header search field, the magnifier icon is
absolutely positioned on the start side (right in RTL, left in LTR)
via inline style, but the input's reserved padding was using
`isRtl ? "pe-9" : "ps-9"`. In RTL, `pe-9` resolves to padding-LEFT —
the opposite side from the icon — so the placeholder text "ابحث في
الملاحظات" started flush to the right edge and visibly overlapped
the icon. In LTR it happened to look correct because both `ps-9` and
the icon's `left: 10` line up.

Fix: pin the padding to the start side unconditionally
(`className="ps-9"`) so it always matches the icon's start-side
placement in both directions. Added an inline comment documenting
the invariant ("icon on start side ⇒ padding on start side") so a
future edit doesn't re-introduce the asymmetry.

Files:
- artifacts/tx-os/src/pages/notes.tsx (search input around line 304-321)

No behavioural changes to LTR rendering, no other search inputs
affected (only this one bound the padding to `isRtl`). Verified with
`pnpm exec tsc --noEmit` (clean). The pre-existing failing `test`
workflow stems from unrelated api-server tsc errors in
executive-meetings.ts and is out of scope for this task.
2026-05-08 11:03:26 +00:00
riyadhafraa 8d207881ef Update default note color to a neutral accent for better contrast
Change the default note's accent color to slate-500 for improved visibility and consistency in `artifacts/tx-os/src/lib/notes-api.ts`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: fccf6ea1-57dc-40bb-8469-1dafe358e639
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-07 15:10:51 +00:00
riyadhafraa 11439abd39 Task #442: Checklist checkbox color matches the note's color
- artifacts/tx-os/src/lib/notes-api.ts: extended NOTE_COLORS so each
  entry carries an `accent` Tailwind class alongside `bg`/`ring`
  (default→amber, red→red, orange→orange, yellow→yellow, green→green,
  teal→teal, blue→sky, purple→purple, pink→pink, gray→slate; all *-500
  for visibility in light + dark). Added a `colorAccent(id)` helper
  that resolves to a literal accent-* class, falling back to the
  default amber for unknown/null ids. The class strings are written
  out as literals so Tailwind v4's source scanner emits all of them
  into the generated CSS — no dynamic class names, no safelist needed.
- artifacts/tx-os/src/pages/notes.tsx: ChecklistView gained an optional
  `noteColor?: string | null` prop and now resolves its checkbox
  accent via `colorAccent(noteColor)` instead of the hard-coded
  `accent-amber-500`. Updated all four call sites (NoteCard, received
  list, sent list, thread dialog) to pass `note.color` / `n.color` /
  `thread.color`.
- artifacts/tx-os/src/components/notes/incoming-note-popup.tsx:
  PopupChecklistPreview gained the same optional `noteColor` prop and
  uses `colorAccent` for the checkbox className. Call site now passes
  `current.color` from the popup queue.
- Unchecked boxes still render with the neutral browser default; only
  the checked accent (the filled tint) follows the note color. The
  default (white/card) note keeps amber for visibility against the
  neutral surface in both themes.
- Verified: `pnpm exec tsc --noEmit` is clean and the
  `tests/notes-popup-on-receive.spec.mjs` regression suite (incl. the
  Task #433 checklist popup case) still passes 3/3.
- Pre-existing failing `test` workflow (api-server tsc errors in
  executive-meetings.ts) is unrelated and out of scope.
2026-05-07 15:08:58 +00:00
riyadhafraa 603aa6a9ec Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4ddd74f2-cd8f-4d72-8d86-956cc7fd8e94
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-07 15:04:10 +00:00
riyadhafraa 28ddbe33d1 Task #441: Fix notification sound on iPad/Safari (Web Audio migration)
- Rewrote artifacts/tx-os/src/lib/notification-sounds.ts to use the
  Web Audio API (lazy AudioContext + decoded AudioBuffer cache) instead
  of HTMLAudioElement. unlock() resumes the ctx for any non-running
  state (handles iOS `interrupted`), plays a 1-sample silent buffer to
  satisfy iOS Safari's gesture requirement, and pre-warms decodes.
  decodeAudioData wrapped to support both Promise and callback forms
  for older WebKit.
- play() dispatches an AUTOPLAY_BLOCKED_EVENT with `{ isIos }` detail
  when ctx isn't running. Throttle is 3 s; new bypassThrottle option
  (used by testPlay) skips the gate AND avoids advancing lastPlayAt
  so settings previews can't suppress real socket chimes.
- use-audio-unlock.ts keeps gesture listeners attached and re-calls
  unlock() on every gesture (handles iPad ctx auto-suspend on
  background / AirPlay).
- use-autoplay-hint.ts reads detail.isIos and switches between
  notifSettings.autoplayHint and notifSettings.autoplayHintIos
  (added to en.json + ar.json with Control Center / silent-switch
  guidance).
- notification-settings.tsx per-sound preview button now calls
  notificationPlayer.testPlay(id).
- Test globals __txosNotifPlayCount/__txosNotifLastSound preserved;
  added __txosNotifCtxState() accessor (dev/test-only, gated by
  import.meta.env.MODE).
- Added tests/notification-sound-webaudio.spec.mjs covering the
  preview button gesture path: counter increments, throttle bypass
  works, singleton AudioContext reaches `running` state.

Notes:
- Pre-existing api-server tsc errors in executive-meetings.ts and the
  failing `test` workflow are unrelated to this task and out of scope.
2026-05-07 14:15:53 +00:00
riyadhafraa be52a88e9d Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: bc5921b3-2ee4-4b41-bfb5-366a51b71525
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-07 14:01:02 +00:00
riyadhafraa 2ac659bf23 Improve drag performance for meeting alerts on touch devices
Apply `touch-action: none` to the drag handle and `pointer-events: none` to the ping animation overlay in `upcoming-meeting-alert.tsx` to fix drag stuttering on iPad.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4114b48d-7679-479b-a57c-9c2f1ab9674b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-07 13:28:28 +00:00
riyadhafraa ee0344a9d3 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.
2026-05-07 11:44:30 +00:00
riyadhafraa 093f42665c 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.
2026-05-07 11:42:38 +00:00
riyadhafraa 10c1e641b4 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.
2026-05-07 11:39:51 +00:00
riyadhafraa 3d8840afee 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.
2026-05-07 11:37:05 +00:00
riyadhafraa 6292d52ffa 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.
2026-05-07 11:34:55 +00:00
riyadhafraa dfbced4476 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.
2026-05-07 11:32:17 +00:00
riyadhafraa f6af6ea5ce 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.
2026-05-07 11:29:46 +00:00
riyadhafraa 725a1a7f27 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.
2026-05-07 11:28:05 +00:00
riyadhafraa a430812998 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.
2026-05-07 11:24:57 +00:00
riyadhafraa 2ced0963be Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 462877ca-477e-467b-8be8-074efb6e2f69
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-07 11:12:12 +00:00
riyadhafraa dcaebb38f5 notes: re-send fires popup + notification again
Previously POST /api/notes/:id/send used onConflictDoNothing on
(noteId, recipientUserId) and only iterated `inserted` rows for
side-effects, so a re-send to the same recipient was silent — no
note_received socket event, no notification — even though the API
returned 200. Reported by the user: "I sent again to rh, why
didn't it reach them?"

Fix: after the idempotent insert, load the existing recipient
rows for any recipients that were skipped, and run the
notification + socket emit loop over BOTH inserted and existing
rows. The DB row is still not duplicated (snapshot semantics
preserved), but the recipient now sees the popup and gets a fresh
notification on every re-send.

Verified:
- @workspace/api-server tests/notes-share.test.mjs — 12/12 pass
  (including "re-sending to the same recipient does not duplicate
  the recipient row").
- tx-os e2e notes-popup-on-receive — 3/3 pass.
2026-05-07 10:32:25 +00:00
riyadhafraa a82ce51979 Update the popup's visual appearance for better consistency
Refactor `IncomingNotePopup` component to simplify structure, adjust background colors, and modify header/footer styling using Tailwind CSS classes.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7c54ec8d-7451-4f17-9a87-e622271d4d8e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-07 10:17:44 +00:00
riyadhafraa eee78810bb Task #436: tint the whole incoming-note popup with the note color
The popup was a colored body sitting inside a white frame; users
wanted the WHOLE popup to look like the sticky note itself, with
only the outer ring + chrome remaining as frame.

- Outer rounded-2xl card now uses `colorBg(current.color)` directly
  (replacing `bg-background`). Default-color notes still render on
  `bg-white dark:bg-card` so the look is unchanged for uncolored
  notes.
- Removed the inner card-in-card colored body wrapper; title +
  content/checklist now render directly on the tinted surface.
- Header divider switched from `bg-muted/40` (opaque chrome) to a
  translucent `border-black/10 dark:border-white/10` so the tint
  flows under the avatar/title/X without an opaque strip.
- Action row moved into its own bordered footer strip (same
  translucent divider) so the 3 buttons stay legible on every
  palette tint while letting the colored surface bleed through.
- Added `data-popup-color` on the card for diagnostics.
- Reply popups inherit the same tint via the existing `color` field
  on the reply payload (no plumbing change).

No payload, locale, or test-id changes. All popup e2e suites pass:
notes-popup-on-receive (3/3), notes-popup-touch-tap (2/2).
2026-05-07 10:16:48 +00:00
riyadhafraa d8eac73679 Task #436: tint the whole incoming-note popup with the note color
The popup was a colored body sitting inside a white frame; users
wanted the WHOLE popup to look like the sticky note itself, with
only the outer ring + chrome remaining as frame.

- Outer rounded-2xl card now uses `colorBg(current.color)` directly
  (replacing `bg-background`). Default-color notes still render on
  `bg-white dark:bg-card` so the look is unchanged for uncolored
  notes.
- Removed the inner card-in-card colored body wrapper; title +
  content/checklist now render directly on the tinted surface.
- Header divider switched from `bg-muted/40` (opaque chrome) to a
  translucent `border-black/10 dark:border-white/10` so the tint
  flows under the avatar/title/X without an opaque strip.
- Action row moved into its own bordered footer strip (same
  translucent divider) so the 3 buttons stay legible on every
  palette tint while letting the colored surface bleed through.
- Added `data-popup-color` on the card for diagnostics.
- Reply popups inherit the same tint via the existing `color` field
  on the reply payload (no plumbing change).

No payload, locale, or test-id changes. All popup e2e suites pass:
notes-popup-on-receive (3/3), notes-popup-touch-tap (2/2).
2026-05-07 10:14:11 +00:00
riyadhafraa 6eb335df1c Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 65ab9d26-9e3a-4167-9393-ae1f277436f2
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/W25NwW4
Replit-Helium-Checkpoint-Created: true
2026-05-07 10:09:56 +00:00
riyadhafraa 21995a5ecb Task #433: incoming-note popup — render full note + 3-button action row
- 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.
2026-05-07 09:34:03 +00:00
riyadhafraa aaafdca1e5 Task #433: incoming-note popup — render full note + 3-button action row
- 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.
2026-05-07 09:32:17 +00:00
riyadhafraa 213895e9e8 Task #433: incoming-note popup — render full note + 3-button action row
- 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.
2026-05-07 09:29:31 +00:00
riyadhafraa ff3c567b3c Task #433: incoming-note popup — render full note + 3-button action row
- 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.
2026-05-07 09:23:29 +00:00
riyadhafraa 03a59572fa Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 46aeb949-fb3b-44c1-bd13-146d32b4eb8c
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/e5Hflyw
Replit-Helium-Checkpoint-Created: true
2026-05-07 09:13:11 +00:00
riyadhafraa ea3092bb2b Task #431: polish Send Note dialog (RTL X + search overlap)
Two visual issues in the "إرسال الملاحظة" dialog:

1. The shadcn DialogContent renders a built-in close (X) button hard
   pinned at `right-4 top-4`. In Arabic the dialog title is
   right-aligned and crashed into the X.
   Fix: add `pr-8` to the SendNoteDialog header so the title row
   reserves physical right-padding matching where the X actually
   lives in both LTR and RTL. (Initially used `pe-8` but corrected to
   `pr-8` after code review noted the close button is pinned with
   physical `right-4`, so a logical end-padding flips to the wrong
   side in RTL.) The shared dialog component is unchanged so other
   dialogs are untouched (out of scope per the task plan).

2. The recipient search field used inline `style={{ right|left: 10 }}`
   for the magnifier and a conditional `pe-9` / `ps-9` based on
   `i18n.language`. In RTL the icon collided with the placeholder.
   Fix: drop the conditional branching — position the icon with the
   logical class `start-2.5` and always pad the input with `ps-9`.
   The icon now sits at the inline-start edge in both LTR and RTL
   with a clear gutter to the placeholder/caret.

Skipped step 3 of the plan (i18n aria-label override on the X). It
required no locale changes and no rendering improvement, so per the
plan's own conditional ("only if step 3 keeps the override; otherwise
no locale changes") it was dropped to keep scope tight.

Verification:
- `pnpm exec tsc --noEmit` clean.
- Playwright: `notes-inbox.spec.mjs` (1/1) and
  `notes-popup-touch-tap.spec.mjs` (2/2) green — both exercise the
  send-note dialog rendering. `notes-popup-on-receive.spec.mjs` runs
  two browser contexts and exceeds the local 120s shell cap; not
  re-run here, but the diff is CSS-only and cannot affect the wire
  flow it covers.
2026-05-07 09:03:28 +00:00
riyadhafraa b5e063d6ed Task #431: polish Send Note dialog (RTL X + search overlap)
Two visual issues in the "إرسال الملاحظة" dialog:

1. The shadcn DialogContent renders a built-in close (X) button hard
   pinned at `right-4 top-4`. In Arabic the dialog title is
   right-aligned and crashed into the X.
   Fix: add `pe-8` to the SendNoteDialog header so the title row
   reserves logical end-padding for the X in both directions. The
   shared dialog component is unchanged so other dialogs are untouched
   (out of scope per the task plan).

2. The recipient search field used inline `style={{ right|left: 10 }}`
   for the magnifier and a conditional `pe-9` / `ps-9` based on
   `i18n.language`. In RTL the icon collided with the placeholder.
   Fix: drop the conditional branching — position the icon with the
   logical class `start-2.5` and always pad the input with `ps-9`.
   The icon now sits at the inline-start edge in both LTR and RTL
   with a clear gutter to the placeholder/caret.

Skipped step 3 of the plan (i18n aria-label override on the X). It
required no locale changes and no rendering improvement, so per the
plan's own conditional ("only if step 3 keeps the override; otherwise
no locale changes") it was dropped to keep scope tight.

Verification:
- `pnpm exec tsc --noEmit` clean.
- Playwright: `notes-inbox.spec.mjs` (1/1) and
  `notes-popup-touch-tap.spec.mjs` (2/2) green — both exercise the
  send-note dialog rendering. `notes-popup-on-receive.spec.mjs` runs
  two browser contexts and exceeds the local 120s shell cap; not
  re-run here, but the diff is CSS-only and cannot affect the wire
  flow it covers.
2026-05-07 09:02:21 +00:00
riyadhafraa 2b52036c25 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 976ef90f-1582-47a4-9191-ee34dfe643eb
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/5kzMIgG
Replit-Helium-Checkpoint-Created: true
2026-05-07 08:55:19 +00:00
riyadhafraa 27bd3cbf61 Task #430: fix folder note creation + add in-app back to folders
Fixes two regressions surfaced after the folder browser shipped in #429:

1. Note created from inside a folder went to Unfiled
   - Composer now takes `folderSelection` and resolves the target folder:
     `folder` → that id, `unfiled`/`all` → null. The new note appears in
     the active folder view immediately and the folder count increments
     via the existing notes query invalidation.

2. No in-app back from a folder view
   - Added a "← Back to folders / المجلدات" chip above the composer
     whenever a folder or Unfiled is selected (active tab only). Tapping
     it resets the selection to "all" and lands the user back on the
     folders browser. The global header back button is unchanged.
   - Chevron flips for RTL via `rtl:rotate-180`.
   - Locale keys: `notes.folders.backToFolders` (EN/AR).

Verification:
- tsc --noEmit clean.
- Playwright suites all green: notes-folders, notes-checklist,
  notes-card-touch-actions, notes-delete-confirm (8 tests).
2026-05-07 08:41:53 +00:00
riyadhafraa 94deea59de Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 688c04a3-6913-4916-8693-3651b206c913
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/5kzMIgG
Replit-Helium-Checkpoint-Created: true
2026-05-07 08:39:14 +00:00
riyadhafraa 9230e487d8 Task #429: folders-as-files browser at "All notes" view
- Notes restored to original sticky-note cards (Section + NoteCard) at all
  views, reverting the file-manager-style note rendering from #427.
- Folders render as a file-manager browser at the top of the active "All
  notes" view: black folder icon, name, relative time, item-count badge,
  "…" kebab with rename/delete, and a checkbox for multi-select. Honors the
  reused grid/list view-toggle.
- Multi-select bar appears when ≥1 folder is checked, with bulk Delete
  (confirmation dialog) and Cancel actions. Bulk delete uses Promise.all
  over useDeleteFolder; selection auto-prunes deleted ids.
- View-toggle is hidden when not at active+all (it now only governs the
  folder browser).
- Added `mode: "rail" | "chips-only"` to FoldersRail. Chips-only is scoped
  to active+all only — archived/inbox/sent rail behavior is unchanged.
- Drop targets preserved (data-folder-drop, folder-drop-${id}) so desktop
  DnD and touch-long-press pipelines keep working.
- Click propagation stopped in DropdownMenuContent and around the checkbox
  so menu/checkbox interactions don't navigate into the folder.
- Removed obsolete FilesView/FileItem/SelectionBar/DeleteDialog/colorTint
  helpers (~650 lines).
- Added EN/AR locale keys: notes.folders.actions, renameFailed, itemCount,
  itemCountOne, select, bulkSelected, bulkDeleteTitle, bulkDeleteConfirm,
  bulkDeleteFailed.
- Verified: tsc --noEmit clean; notes-folders, notes-card-touch-actions,
  notes-checklist, notes-delete-confirm Playwright suites all green.

Drift note: kept "All notes" as a mixed surface (folders browser + sticky
notes below the composer) per the user's task wording, which explicitly
requires both sticky-note cards and file-manager folders. Code reviewer
suggested folders-only as primary surface, but doing so would hide notes
the user explicitly asked to see and break tests at the default view.
2026-05-07 08:22:37 +00:00
riyadhafraa 74e9272c2a Task #429: folders-as-files browser at "All notes" view
- Notes restored to original sticky-note cards (Section + NoteCard) at all
  views, reverting the file-manager-style note rendering from #427.
- Folders now render as a file-manager browser at the top of the "All notes"
  view: black folder icon, name, relative time, item-count badge, "…" kebab
  with rename/delete, plus a checkbox for multi-select.
- Multi-select bar appears when ≥1 folder is checked, with bulk Delete
  (confirmation dialog) and Cancel actions. Bulk delete uses Promise.all
  over useDeleteFolder; selection auto-prunes deleted ids.
- Grid/list view-toggle reused from #427; toggle is hidden when not at
  "All notes" since it now only governs the folder browser.
- Added `mode: "rail" | "chips-only"` to FoldersRail. At "All notes" the
  rail switches to chips-only (All / Unfiled / + New folder) so folder rows
  live exclusively in the new browser and test ids never collide.
- Drop targets preserved (data-folder-drop, folder-drop-${id}) so the
  desktop DnD and touch-long-press pipelines keep working.
- Stop click propagation in DropdownMenuContent and around the checkbox to
  prevent rename/delete/checkbox clicks from navigating into the folder.
- Removed obsolete FilesView/FileItem/SelectionBar/DeleteDialog/colorTint
  helpers (~650 lines).
- Added EN/AR locale keys: notes.folders.actions, renameFailed, itemCount,
  itemCountOne, select, bulkSelected, bulkDeleteTitle, bulkDeleteConfirm,
  bulkDeleteFailed.
- Verified: tsc --noEmit clean; all 8 tests in notes-folders,
  notes-card-touch-actions, notes-checklist, notes-delete-confirm pass.
2026-05-07 08:19:50 +00:00
riyadhafraa 25f624f8d8 Task #429: folders-as-files browser at "all" notes view
- Notes reverted to original sticky-note cards (Section + NoteCard) at all
  views, removing the file-manager-style note rendering from task #427.
- Folders now render as a file-manager browser at the top of the "All notes"
  view: big black folder icon, name, relative time, item-count badge, and a
  "…" kebab with rename/delete. Grid and list layouts honor the existing
  view-toggle, plus a "{{count}} folders" footer.
- Added `mode: "rail" | "chips-only"` prop to FoldersRail. At "all" view the
  rail switches to chips-only (All / Unfiled / + New folder) so folder rows
  live exclusively in the new browser and test ids never collide.
- View-toggle is now hidden when not at "all" (folders-only control).
- New FoldersBrowser/FolderItem reuse useUpdateFolder/useDeleteFolder, set
  data-folder-drop for the touch DnD pipeline, and stop click propagation
  inside the dropdown so rename/delete don't navigate the card.
- Added locale keys: notes.folders.actions/renameFailed/itemCount(One).
- Removed obsolete FilesView/FileItem/SelectionBar/DeleteDialog/colorTint
  helpers (~650 lines).
- Verified: tsc --noEmit clean; notes-folders, notes-card-touch-actions,
  notes-checklist, notes-delete-confirm Playwright suites all green.
2026-05-07 08:14:23 +00:00
riyadhafraa 097baddddb Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: eaceab9d-0e1b-4aa6-ab56-3e69031fcc59
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/5kzMIgG
Replit-Helium-Checkpoint-Created: true
2026-05-07 07:59:36 +00:00
riyadhafraa 1676a54ff8 Add file manager view to notes section with grid and list options
Adds new translation keys for UI elements and implements a view mode toggle (grid/list) for the notes page, persisting the selection in local storage.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 88b40c3d-8ff3-492f-891b-c2b0853ab41d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/5kzMIgG
Replit-Helium-Checkpoint-Created: true
2026-05-07 07:52:50 +00:00
riyadhafraa fc14ca4c01 Task #427: fix floating note popup hanging on touch devices
Root cause on iOS Safari (iPad/phone): the `IncomingNotePopup`
rendered a `fixed inset-0 z-[110]` full-viewport wrapper. Stacking
that layer on top of the upcoming-meeting alert plus a Radix toast
intermittently caused the next finger tap to be swallowed — the user
saw the popup "hang". A simultaneous re-render storm from
synchronous query invalidations on multi-event socket fanouts made
it worse on slower hardware.

Fixes:
- Drop the full-viewport wrapper: render the popup card directly
  as a sized `fixed` element. No more invisible viewport-sized
  layer between touch and the rest of the UI.
- Suppress the redundant note/reply toast when the floating
  popup actually accepts the event. The popup IS the surface;
  doubling up just stacks one more floating layer.
- Remove `autoFocus` on the Reply button — focus-steal was
  contributing to the perceived hang.
- Coalesce `react-query` `invalidateQueries` calls in the
  notifications socket: collect into a Set and flush once per
  animation frame so a burst of socket events triggers a single
  refetch pass instead of N synchronous re-renders. Cleaned up
  on socket disconnect.

Tests (artifacts/tx-os/tests/notes-popup-touch-tap.spec.mjs):
- "recipient on a touch device can tap Reply on the floating
  note popup" — verifies the popup is no longer viewport-sized,
  the duplicate toast is suppressed, and a real `locator.tap()`
  succeeds.
- "popup + meeting alert simultaneously: tap on popup still
  works" — the user's exact scenario: seeds an imminent meeting
  so the alert appears, triggers a real cross-user note
  delivery so both float simultaneously, then taps Reply on
  the popup and confirms it dismisses while the alert remains.

Both tests + tsc --noEmit clean.

Files:
- artifacts/tx-os/src/components/notes/incoming-note-popup.tsx
- artifacts/tx-os/src/hooks/use-notifications-socket.ts
- artifacts/tx-os/tests/notes-popup-touch-tap.spec.mjs (new)
2026-05-06 11:54:13 +00:00
riyadhafraa e283a83162 Task #427: fix floating note popup hanging on touch devices
Root cause on iOS Safari (iPad/phone): the `IncomingNotePopup`
rendered a `fixed inset-0 z-[110]` full-viewport wrapper with
`pointer-events-none`. Stacking that layer on top of the upcoming
meeting alert plus a Radix toast intermittently caused the next
finger tap to be swallowed — the user saw the popup "hang".

Fixes:
- Drop the full-viewport wrapper: render the popup card directly
  as a sized `fixed` element. No more invisible viewport-sized
  layer between touch and the rest of the UI.
- Suppress the redundant note/reply toast when the floating
  popup actually accepts the event. The popup IS the surface;
  doubling up just stacks one more floating layer.
- Remove `autoFocus` on the Reply button. A new popup arriving
  while the user is mid-tap would steal focus, contributing to
  the perceived hang.

Tests:
- New `notes-popup-touch-tap.spec.mjs`: emulates a touch viewport,
  sends a real cross-user note, verifies popup bounding box is
  card-sized (not viewport-sized), the redundant toast is
  suppressed, and a `locator.tap()` on Reply dismisses the
  popup and navigates.
- `tsc --noEmit` clean.

Files:
- artifacts/tx-os/src/components/notes/incoming-note-popup.tsx
- artifacts/tx-os/src/hooks/use-notifications-socket.ts
- artifacts/tx-os/tests/notes-popup-touch-tap.spec.mjs (new)
2026-05-06 11:43:23 +00:00