Commit Graph

783 Commits

Author SHA1 Message Date
riyadhafraa b1b77395d0 #486 Executive Meetings: row-click quick actions popover (Move up / Move down / Postpone)
Clicking any meeting row on the schedule now opens a small popover with three
quick actions: Move up, Move down, and Postpone. Gated only on canMutate (NOT
editMode) per spec.

Backend
- New POST /executive-meetings/swap-times endpoint (artifacts/api-server/src/
  routes/executive-meetings.ts). FOR UPDATE locks both rows by ascending id to
  avoid deadlocks, optimistic-lock check via expectedUpdatedAt{A,B} (returns
  409 stale_meeting + conflict.lastActor — same shape PostponeDialog
  understands), guards different_dates and no_time_window, swaps only
  (startTime, endTime), audits each row as `meeting_swap_times`, calls
  renumberDayByStartTime so the # column matches the new chronological order,
  and broadcasts emitExecutiveMeetingsDayChanged.
- New zod body schema ExecutiveMeetingsSwapTimesBody in lib/api-zod/src/manual.ts.

Frontend
- New shared lib/api-json.ts (ApiError + apiJson) extracted from upcoming-
  meeting-alert.tsx so the page can reuse the same fetch/error contract.
- upcoming-meeting-alert.tsx exports PostponeDialog so the page can mount it
  for quick-action postpone without duplicating UI.
- artifacts/tx-os/src/pages/executive-meetings.tsx: Schedule wires
  swapTimes/quickMoveUp/quickMoveDown/postpone state, computes per-day
  neighbours via meetingNumbersById, and mounts a single page-level
  PostponeDialog. MeetingRow wraps `<tr>` in a Popover/PopoverAnchor; row
  onClick opens the popover with skip rules for buttons/inputs/contenteditable
  and ARIA roles (button/checkbox/switch/combobox/dialog) plus testid
  prefixes (em-row-grip/-actions/-select, em-edit-*, em-merge-edit-*, em-time-*)
  so the time cell's inline editor and other affordances don't collide.
- en/ar locales gain executiveMeetings.quickActions.{label,moveUp,moveDown,postpone}.

Tests
- artifacts/api-server/tests/executive-meetings-swap-times.test.mjs (new):
  happy path, 409 stale_meeting (with conflict actor), 400 different_dates,
  400 no_time_window. Each scenario uses a distinct far-future date to avoid
  daily_number races with seeded data.
- artifacts/tx-os/tests/executive-meetings-row-quick-actions.spec.mjs (new):
  drives the date input, verifies row click → popover, Move up swap reflected
  in DB, and Postpone item opens the dialog.

Code review (architect) flagged an edit-mode conflict where the time cell
(role=button div) would also bubble to the row handler; fixed by adding ARIA
role and em-time-* skip rules. Architect also flagged keyboard-trigger gap
and edit-mode test gaps — proposed as follow-ups.

Other test failures in the repo (executive-meetings reorder, font-settings,
notes-share, service-orders) pre-date this change and are unrelated.
2026-05-11 10:55:34 +00:00
riyadhafraa 2e364a102e 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: 45aa8815-ec0b-41f7-959c-2ed3faf96552
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw
Replit-Helium-Checkpoint-Created: true
2026-05-11 10:23:45 +00:00
riyadhafraa 2d131fddf0 #484 Reset meetings edit mode on leaving the app
The Executive Meetings page persisted its global "Edit / View" toggle
in localStorage (key `em-schedule-edit-mode-v1:<userId>`). Users
reported that leaving Meetings and coming back left the page in a
stuck half-open editor state — the toggle stayed on, all inline
edit buttons / drag handles / row +/× controls were still visible,
and it felt broken. Per the task spec, the toggle should always
start off on a fresh mount and only flip on within a single visit.

Source change (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Removed the `EDIT_MODE_STORAGE_KEY` constant (replaced with a
  short comment explaining the non-persistence decision).
- Dropped the `editModeStorageKey` per-user namespaced memo.
- Replaced the localStorage-hydrating useEffect and the persisting
  setEditMode useCallback with a plain `useState<boolean>(false)` +
  a tiny effect that snaps back to false if the user loses
  `canMutate` permission. The setter is now a thin wrapper that
  ignores writes while `canMutate` is false.
- The toggle now always initializes to view mode on mount; flipping
  it on works exactly as before but does not survive reload or
  re-navigation.

Test changes (artifacts/tx-os/tests/executive-meetings-edit-toggle.spec.mjs):
- Renamed the existing "Edit toggle hides editing affordances..." test
  to "...and resets to view mode on reload / re-navigation (#484)".
- Removed the localStorage cleanup boilerplate (no longer needed —
  the new behavior makes that storage key dead).
- Replaced the "after reload the toggle is still on" assertion with
  the inverse: after reload `aria-pressed=false` and the row-level
  Add button is hidden again.
- Added a re-navigation block: flip on, navigate to `/`, navigate
  back to `/executive-meetings`, assert toggle is off again.
- Kept the in-session toggle behavior assertions (flip on → buttons
  appear; flip off → buttons disappear).

Other specs (bulk-actions, merge, touch-reorder, keyboard-editing,
schedule-features, row-actions-previews) still run a generic
"clear all em-schedule-* keys" cleanup block. Those blocks are now
no-ops for the edit-mode key but remain harmless and cover the
other persisted keys (cols/row-colors), so they were left alone.

Verified: `tsc --noEmit` clean; the new "Edit toggle hides ... and
resets to view mode on reload / re-navigation (#484)" test passes.

Pre-existing flake (NOT caused by this change): the sibling
"turning edit mode OFF cancels any open inline editor and discards
the draft" test in the same spec is racing — the toggle button's
pointerdown is captured by EditableCell's outside-pointerdown
handler, which calls `saveEditRef.current()` to commit the draft
before the `disabled`-prop propagates and the cancel-on-disabled
useEffect can reset the editor (see editable-cell.tsx ~lines
295-307 vs ~421-426). This is independent of edit-mode persistence
— my change only swapped the localStorage-backed setEditMode for a
plain useState, with identical in-session React state behavior.
Fixing the EditableCell race is out of scope for #484.

Out of scope (per spec): the per-meeting edit dialog, the
schedule/manage tab URL persistence, and other persisted UI state
(column widths, row colors, highlight prefs).
2026-05-11 10:02:36 +00:00
riyadhafraa f8b0969cd2 Improve responsiveness for meeting reschedule dialog
Adjust grid breakpoints for the postpone dialog and update RTL test assertions.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 370d4507-cc98-4781-a35f-85ce37566751
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw
Replit-Helium-Checkpoint-Created: true
2026-05-11 08:12:08 +00:00
riyadhafraa 1f225298b1 #485 Fix overlapping layout in the Postpone meeting dialog
In Arabic/RTL the Postpone dialog (opened from the upcoming-meeting
alert) was visibly overlapping its own borders on two tabs:
1. Postpone tab → cascade preview ("الاجتماعات المتأثرة"): the times
   column ran wider than the pink panel, the title column pushed it
   off-screen, and the meeting-# header wrapped to two lines while
   the others stayed on one.
2. Reschedule tab: three native date / start / end inputs forced into
   md:grid-cols-3 inside an sm:max-w-md dialog, which clipped the
   right-most input against the dialog frame in RTL.

Both issues were pure layout — the dialog was simply too narrow for
its content.

Changes (artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx):
- Bumped the postpone DialogContent from `sm:max-w-md` (≈448 px) to
  `sm:max-w-2xl` (≈672 px). Still full width on phones, capped on
  desktop. Reschedule's existing `grid-cols-1 sm:grid-cols-2
  md:grid-cols-3` now has room to actually render three inputs at md
  without clipping (Input already defaults to w-full).
- CascadePromptBlock affected-meetings table: switched to
  `table-fixed` with an explicit `<colgroup>` (w-20 number / flex
  title / w-40 times). The number column was bumped from w-12 to
  w-20 so the Arabic header "رقم الاجتماع" + `whitespace-nowrap`
  has room at common font sizes (raised during code review). Title
  cell keeps its `truncate` + `title=` tooltip but no longer needs
  `max-w-[180px]`. Added `whitespace-nowrap` to # and times headers
  so the latter never wraps. Wrapper got `overflow-x-hidden` to
  belt-and-suspenders the no-horizontal-scroll guarantee. Action
  button row already used `flex-wrap` — preserved.

Tests (artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs):
- Added a Reschedule-tab regression test that opens the dialog at
  1280×800 in Arabic and asserts the bounding boxes of
  `reschedule-date` / `reschedule-start` / `reschedule-end` all sit
  inside the dialog's bounding box (1 px tolerance).
- Added a Cascade-table regression test (raised during code review)
  that seeds a primary + 2 followers, postpones to fire the cascade
  prompt, and asserts the `cascade-followers-list` and the last
  `<td>` (times) of the first follower row both sit inside the
  dialog at desktop width in RTL.
- Confirmed no behavior regressions across the postpone-by-10,
  cascade follower display, tab-switching, and reschedule-to-tomorrow
  tests.

Out of scope (per task spec): backend cascade/postpone logic, the
floating alert panel itself, dialog visual restyling, and the Cancel
tab beyond what comes "for free" with the wider dialog.

The pre-existing `test` workflow failures (executive-meetings reorder,
font-settings roundtrip, notes-share recipient PATCH 403/404) are
unrelated to this layout change and were already failing before.
2026-05-11 08:08:54 +00:00
riyadhafraa 21bac11aa5 #485 Fix overlapping layout in the Postpone meeting dialog
In Arabic/RTL the Postpone dialog (opened from the upcoming-meeting
alert) was visibly overlapping its own borders on two tabs:
1. Postpone tab → cascade preview ("الاجتماعات المتأثرة"): the times
   column ran wider than the pink panel, the title column pushed it
   off-screen, and the meeting-# header wrapped to two lines while
   the others stayed on one.
2. Reschedule tab: three native date / start / end inputs forced into
   md:grid-cols-3 inside an sm:max-w-md dialog, which clipped the
   right-most input against the dialog frame in RTL.

Both issues were pure layout — the dialog was simply too narrow for
its content.

Changes (artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx):
- Bumped the postpone DialogContent from `sm:max-w-md` (≈448 px) to
  `sm:max-w-2xl` (≈672 px). Still full width on phones, capped on
  desktop. Reschedule's existing `grid-cols-1 sm:grid-cols-2
  md:grid-cols-3` now has room to actually render three inputs at md
  without clipping (Input already defaults to w-full).
- CascadePromptBlock affected-meetings table: switched to
  `table-fixed` with an explicit `<colgroup>` (w-12 number / flex
  title / w-40 times). Title cell keeps its `truncate` + `title=`
  tooltip but no longer needs `max-w-[180px]`. Added `whitespace-nowrap`
  to # and times headers so the latter never wraps. Wrapper got
  `overflow-x-hidden` to belt-and-suspenders the no-horizontal-scroll
  guarantee. Action button row already used `flex-wrap` — preserved.

Tests (artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs):
- Added a regression test that opens the dialog at 1280×800 in
  Arabic, switches to the Reschedule tab, and asserts the bounding
  boxes of `reschedule-date` / `reschedule-start` / `reschedule-end`
  all sit inside the dialog's bounding box (1 px tolerance).
- Re-ran the full upcoming-alert spec to confirm no behavior
  regressions in postpone-by-minutes, cascade prompts, cancel,
  reschedule-to-tomorrow, dismiss, or the alert position-clamp test.

Out of scope (per task spec): backend cascade/postpone logic, the
floating alert panel itself, dialog visual restyling, and the Cancel
tab beyond what comes "for free" with the wider dialog.

The pre-existing `test` workflow failures (executive-meetings reorder,
font-settings roundtrip, notes-share recipient PATCH 403/404) are
unrelated to this layout change and were already failing before.
2026-05-11 08:05:51 +00:00
riyadhafraa e387862e38 Stop edit mode from persisting across user sessions
Prevent edit mode state from being saved in localStorage, ensuring it resets on page load.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 72d23999-40af-4930-a6a3-74321b4ed005
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw
Replit-Helium-Checkpoint-Created: true
2026-05-11 07:59:41 +00:00
riyadhafraa 0e36463572 Add images related to the application's functionality
Add new image assets to the attached_assets directory.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a9d1d3c6-ed21-4007-9a88-f31d5fe6d488
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw
Replit-Helium-Checkpoint-Created: true
2026-05-11 07:56:19 +00:00
riyadhafraa 2b31b5e3aa Remove title field from notes and update tests to reflect changes
Refactors the notes feature by removing the `title` field and updating all related tests and UI components to use `content` instead.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7464969c-726a-4ec2-a3b2-8ff63f91f6ed
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw
Replit-Helium-Checkpoint-Created: true
2026-05-11 07:53:40 +00:00
riyadhafraa a8db38c70a 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: 684521f6-b89f-4401-ba07-ccbceb33161d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh
Replit-Helium-Checkpoint-Created: true
2026-05-11 07:39:55 +00:00
riyadhafraa d6b19748f7 Update upcoming meeting alert to better display cascade information
Refactor upcoming meeting alert component to update the cascade prompt table structure and adjust end-to-end tests to reflect the changes in column count and content assertions.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 9509e0a2-e6ad-4b70-8d04-56b729a8f67c
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh
Replit-Helium-Checkpoint-Created: true
2026-05-10 16:09:15 +00:00
riyadhafraa 7ea7744e26 #481 Polish cascade-affected meetings table in postpone/reschedule prompt
- Drop the leading "#" index column; dailyNumber already serves as a
  stable per-row identifier.
- Replace the hard-coded amber background/border with the user's chosen
  alert accent via hexToRgba(accent, 0.12 / 0.45). Threaded `accent`
  through PostponeDialog → CascadePromptBlock so children don't read
  prefs directly. Both the loading and main prompt blocks track the
  accent; the rose blocked-by-midnight variant is intentionally kept.
- Render times as localized 12-hour with ص/م (AR) or AM/PM (EN) by
  reusing the shared formatTime helper instead of slicing "HH:mm".
- Drop now-unused i18n key cascadeColIndex from ar.json + en.json.
- Update the e2e test to match the 3-column schema (meeting#, title,
  times); kept the dailyNumber assertion in the first cell.

tsc clean. Cascade specs pass (Postpone by 10, Reschedule cascade,
Cascade prompt UI: Shift/Keep, no-followers fallthrough).
2026-05-10 16:07:20 +00:00
riyadhafraa 4994840989 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: dacadc17-acec-4e7e-95dc-901fee1ff7bb
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh
Replit-Helium-Checkpoint-Created: true
2026-05-10 16:00:33 +00:00
riyadhafraa f429b57cf3 Improve meeting alert accessibility by using semantic table headers
Update table header cells in the upcoming meeting alert component to use `<th>` elements with `scope="row"` for improved accessibility, while maintaining visual styling.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: cfc95efc-e616-467b-a131-64907741bd55
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh
Replit-Helium-Checkpoint-Created: true
2026-05-10 15:45:53 +00:00
riyadhafraa 5e37d392c0 #479: Render upcoming-alert attendees as numbered tables per group
- DetailsPanel in upcoming-meeting-alert.tsx: replaced the per-group
  `<ul class="list-disc">` with a real `<table class="w-full">`. Each
  attendee row is `<tr>` with two `<td>`s: a narrow tabular-nums index
  cell (`{idx + 1}.`, scope="row") and the cleaned name cell. The
  group heading is rendered as `<caption>` (text-start, font-bold,
  text-xs) so it spans both columns and is announced as the table
  caption to AT. Each table also carries `aria-label={group.heading}`.
- Numbering restarts at 1 per group (each group is its own table); a
  code comment notes the one-line tweak to use a continuous count.
- Preserved: `data-testid="alert-details-attendees"` wrapper,
  `max-h-40 overflow-y-auto` scroll, empty-state, location/URL rows,
  and the section header total ("الحضور (n)"). RTL/LTR work via
  `text-start` and `pe-1`.
- e2e: added a new test in
  `artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs`
  that seeds a meeting with two subheading-delimited groups (3
  external + 1 internal), expands details, and asserts two tables
  render with rows starting at "1." in each group.

Verification:
- `pnpm exec tsc --noEmit` clean.
- New attendees-table spec passes (1/1, 16.5s).

No server, schema, or i18n key changes; Tailwind utilities only.
2026-05-10 15:44:23 +00:00
riyadhafraa ef00f48d23 Update translations for cascade meeting list notes
Add and update localization strings for the cascade meeting list, including a note about `cascadeListItem` being intentionally kept for backward compatibility.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7ea15d6b-48ed-4f23-a920-ac6569ce3a81
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh
Replit-Helium-Checkpoint-Created: true
2026-05-10 15:41:04 +00:00
riyadhafraa ae31ed90c6 #480 Show cascade-affected meetings as a numbered table
Replace the simple <ul> in the cascade-prompt panel inside the upcoming-
meeting alert with a 4-column table: row index, the meeting's
dailyNumber, title (truncate + title=), and "from → to". The new
`meetingNumbersById` map is built once in UpcomingMeetingAlert from
the already-loaded dayData and threaded through PostponeDialog into
both CascadePromptBlock call sites (postpone-minutes and reschedule).
Existing `cascade-followers-list` and `cascade-follower-<id>` testids
are preserved on the table/rows so prior selectors keep matching.

Adds 4 new i18n keys (cascadeColIndex/MeetingNumber/Title/Times) in
ar.json and en.json. The legacy cascadeListItem key is left in place
since it isn't worth a separate cleanup.

Extends the existing "Cascade prompt UI: Shift sends cascadeFollowing"
e2e test to assert the first row's index cell renders "1" and the
meeting-number cell renders the seeded follower's dailyNumber.
insertImminentMeeting now also returns dailyNumber so the assertion
can read it. tsc clean; targeted cascade specs pass (3/3).
2026-05-10 15:40:17 +00:00
riyadhafraa 8b91560f77 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: cfb86bd9-81b1-4049-97f1-049af243618f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh
Replit-Helium-Checkpoint-Created: true
2026-05-10 15:33:55 +00:00
riyadhafraa 62e7be4c42 alert: keep meeting reminder buttons clickable while a Dialog is open
Task #478. When the upcoming-meeting reminder ("الاجتماع التالي")
appeared on top of an open note thread dialog, the user could not
tap "تأجيل" / "تم" / × / "تفاصيل". The buttons looked enabled and
the popup sat above the dialog overlay (`z-[60]` over `z-50`), but
nothing happened on click.

Root cause: Radix `Dialog` is modal by default and its scroll-lock
helper (`react-remove-scroll`) sets `pointer-events: none` on the
document body while open. The meeting alert is portaled to body and
inherited that, silently swallowing all clicks.

Fix: in `artifacts/tx-os/src/components/executive-meetings/upcoming-
meeting-alert.tsx`, add `pointer-events-auto` to the floating panel's
className so it explicitly opts back in to receiving pointer events.
Buttons inside inherit auto from the panel. Inline comment added
referencing #478 and the Radix scroll-lock behavior.

No change to the shared `Dialog` primitive, no other tokens or
components touched. Existing `#282` behavior (hide alert while its
own postpone modal is open) is preserved.

Verified:
- `tsc --noEmit` clean.
- `notes-thread-dialog.spec.mjs` passes (no regression in the
  scenario that surfaced the bug).
2026-05-10 15:29:14 +00:00
riyadhafraa 3d23f597c5 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: 0d14e3a3-cc8f-46c8-868c-16b7c2290261
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8BABrKh
Replit-Helium-Checkpoint-Created: true
2026-05-10 15:27:40 +00:00
riyadhafraa 951fcce26b theme: lighten primary navy from #0B1E3F to #2C4A8A
Task #477. The brand primary color was a very dark navy (218 70% 15%
≈ #0B1E3F) and dominated buttons, the active sidebar row, the FAB,
and focus rings. User asked to lighten it while keeping the same blue
family.

Change: in `artifacts/tx-os/src/index.css` (`:root`), bumped four
tokens to `218 55% 32%` (≈ #2C4A8A) and updated the inline hex
comment:
- `--primary`
- `--ring`
- `--sidebar-primary`
- `--sidebar-ring`

White text on the new primary keeps WCAG AA contrast (≈5.3:1), so the
existing `--primary-foreground: 0 0% 100%` is preserved.

No other tokens (background, foreground, accent, secondary, destructive,
borders) were touched. No component-level overrides were modified.
2026-05-10 15:23:39 +00:00
riyadhafraa 3f993de19d 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: bc96bde4-62dc-4348-acb5-d6ed1534c5f6
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 15:22:45 +00:00
riyadhafraa 76f2f7ce51 notes(ipad): keep dialog and inputs above the on-screen keyboard
Task #476. On iPad Safari, opening the keyboard while writing a new
note or replying inside the note thread dialog covered the bottom of
the dialog (textarea + send button). The dialog used
`top:50%; translate(-50%,-50%)` against the layout viewport and never
reacted to the visual viewport shrinking when the keyboard appeared.

Changes:
- artifacts/tx-os/index.html: added `interactive-widget=resizes-content`
  to the viewport meta so iOS 17+ resizes the layout viewport when the
  software keyboard opens.
- artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that
  subscribes to `window.visualViewport` `resize` / `scroll` /
  `orientationchange` (rAF-throttled) and exposes the visible height,
  the visual viewport `offsetTop`, and the bottom keyboard inset.
- artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now
  reads the hook and, only when the keyboard inset exceeds ~80px,
  pins its center to `offsetTop + height/2` and caps `max-height` to
  the visible height. With the existing `translate(-50%,-50%)`, both
  edges stay inside the visible region above the keyboard. Desktop
  unaffected (inset is 0 → no inline style applied).
- artifacts/tx-os/src/pages/notes.tsx: thread reply textarea and the
  top composer textarea now call `scrollIntoView({ block: "center" })`
  on focus and again whenever the keyboard inset changes while the
  field is focused. Both paths are gated on `keyboardInset > 80`, so
  desktop focus is untouched.

Verification:
- `tsc --noEmit` clean.
- e2e: `notes-thread-dialog` and `notes-composer-color` both pass.
2026-05-10 15:18:54 +00:00
riyadhafraa db6e7726eb notes(ipad): keep dialog above the on-screen keyboard
Task #476. On iPad Safari, opening the keyboard while writing a new
note or replying inside the note thread dialog covered the bottom of
the dialog (textarea + send button). The dialog used
`top:50%; translate(-50%,-50%)` against the layout viewport and never
reacted to the visual viewport shrinking when the keyboard appeared.

Changes:
- artifacts/tx-os/index.html: added `interactive-widget=resizes-content`
  to the viewport meta so iOS 17+ resizes the layout viewport when the
  software keyboard opens.
- artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that
  subscribes to `window.visualViewport` `resize` / `scroll` /
  `orientationchange` (rAF-throttled) and exposes the visible height,
  the visual viewport `offsetTop`, and the bottom keyboard inset.
- artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now
  reads the hook and, only when the keyboard inset exceeds ~80px
  (i.e. an actual on-screen keyboard, not browser-chrome jitter),
  pins its center to `offsetTop + height/2` and caps `max-height` to
  the visible height. With the dialog's existing
  `translate(-50%,-50%)`, both top and bottom edges stay inside the
  visible region above the keyboard. Desktop renders unchanged
  (inset is 0 → no inline style applied beyond what the caller passes).

Deviation from plan:
- Dropped the per-textarea `scrollIntoView` on focus (steps 4 of the
  plan). Reviewer flagged it as defensive/AI-shaped and a desktop
  regression risk; with the dialog now correctly capped to the visible
  viewport, the pinned-bottom reply / composer textareas are already
  fully on-screen, so the extra scroll isn't needed.

Verification:
- `tsc --noEmit` clean.
- `notes-thread-dialog` e2e still passes.
2026-05-10 15:15:47 +00:00
riyadhafraa 5dac662421 notes(ipad): keep dialog above the on-screen keyboard
Task #476. On iPad Safari, opening the keyboard while writing a new
note or replying inside the note thread dialog covered the bottom of
the dialog (textarea + send button). The dialog used
`top:50%; translate(-50%,-50%)` against the layout viewport and never
reacted to the visual viewport shrinking when the keyboard appeared.

Changes:
- artifacts/tx-os/index.html: added `interactive-widget=resizes-content`
  to the viewport meta so iOS 17+ resizes the layout viewport when the
  software keyboard opens.
- artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that
  subscribes to `window.visualViewport` `resize` / `scroll` /
  `orientationchange` events (rAF-throttled) and exposes the current
  visible height plus the bottom keyboard inset.
- artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now
  reads the hook and, when the keyboard inset is non-trivial (> 80px),
  pins its center to the visible viewport mid-line and caps
  `max-height` to the visible height. Desktop renders unchanged
  (inset is 0 → no inline style applied beyond what the caller passes).
- artifacts/tx-os/src/pages/notes.tsx: the thread dialog reply
  textarea and the top composer textarea now `scrollIntoView({ block:
  "center" })` on focus (after a 250ms delay so the keyboard animation
  settles), so the cursor lands inside the visible region on touch
  devices. Desktop focus is unaffected — `scrollIntoView` is a no-op
  when the element is already in the viewport.

Verification:
- `tsc --noEmit` clean.
- Existing `notes-thread-dialog` e2e still passes (dialog stays
  capped at 85% viewport height, scroller still overflows, recipient
  chips and composer remain in viewport).
2026-05-10 15:13:21 +00:00
riyadhafraa 23da13be79 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: eb67b14b-72ca-4dc7-a03d-c38c2c7748de
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 15:11:32 +00:00
riyadhafraa 36f6d1f5e6 notes(shared): show Send button on notes inside shared folders
User report (AR, RH on iPad): no Send button visible inside a shared
"desk" folder. The shared-folder view was passing `hideSend` to every
NoteCard for editors and rendered no card actions at all for
read-only viewers, so nobody could send a note from a shared folder.

Changes (artifacts/tx-os/src/pages/notes.tsx, SharedFolderView):
- Editors: removed `hideSend` from the NoteCard so the existing Send
  affordance (and color/labels/archive/delete) appears as it does on
  the user's own notes.
- Read-only viewers: added a small Send icon button to the static
  card (testid `shared-folder-note-send-<id>`) so they can also send
  the note via the existing send dialog.

The send endpoint authorizes off the authenticated caller, so the
note is sent under RH's identity (no server change needed).
2026-05-10 15:03:22 +00:00
riyadhafraa d0e907e409 notes(folders): allow drag-drop of notes onto shared folders (edit perm)
User report (AR): "shared folders also appear here — the user should
be able to move notes wherever they want." The folders rail already
lists shared-with-me folders, but only OWN folder rows registered as
@dnd-kit drop targets. Recipients with edit permission could see a
shared folder but couldn't drop a note onto it.

Changes:
- folders-rail.tsx: extend the inline `DroppableRow` to accept an
  optional `ownerId`. Wrap each shared-folder row in a DroppableRow
  when `sf.myPermission === "edit"`, passing the owning user's id;
  read-only viewers stay non-droppable. The droppable id is namespaced
  with `-shared-<ownerId>` so it never collides with the owner-side
  row for the same folder id.
- notes.tsx: extend `sharedFolderBucketRef` to carry the bucket's
  `ownerId` (sourced from `data.folder.ownerId`). In `handleDragEnd`,
  read `o.ownerId` from the drop data:
    • undefined  → own folder / Unfiled. Allow only own notes.
    • number     → shared folder. Allow only when the source note
                   came from sharedBucket and `sharedBucket.ownerId
                   === o.ownerId`.
  This mirrors the server-side rule that an editor may move notes
  only between folders owned by the same owner — cross-owner drops
  are blocked client-side so we never round-trip a 400.

Out of scope:
- Cross-owner moves (would require a copy/share flow, not supported
  by the server today).
- Drop targets on shared folders inside the FoldersBrowser tile grid
  (the rail covers the user's reported screenshot; tiles can follow).
2026-05-10 14:56:33 +00:00
riyadhafraa 2629588f32 notes(folders): allow drag-drop of notes onto shared folders (edit perm)
User report (AR): "shared folders also appear here — the user should
be able to move notes wherever they want." The folders rail already
lists shared-with-me folders, but only OWN folder rows registered as
@dnd-kit drop targets. Recipients with edit permission could see a
shared folder but couldn't drop a note onto it.

Changes:
- folders-rail.tsx: extend the inline `DroppableRow` to accept an
  optional `ownerId`. Wrap each shared-folder row in a DroppableRow
  when `sf.myPermission === "edit"`, passing the owning user's id;
  read-only viewers stay non-droppable. The droppable id is namespaced
  with `-shared-<ownerId>` so it never collides with the owner-side
  row for the same folder id.
- notes.tsx: extend `sharedFolderBucketRef` to carry the bucket's
  `ownerId` (sourced from `data.folder.ownerId`). In `handleDragEnd`,
  read `o.ownerId` from the drop data:
    • undefined  → own folder / Unfiled. Allow only own notes.
    • number     → shared folder. Allow only when the source note
                   came from sharedBucket and `sharedBucket.ownerId
                   === o.ownerId`.
  This mirrors the server-side rule that an editor may move notes
  only between folders owned by the same owner — cross-owner drops
  are blocked client-side so we never round-trip a 400.

Out of scope:
- Cross-owner moves (would require a copy/share flow, not supported
  by the server today).
- Drop targets on shared folders inside the FoldersBrowser tile grid
  (the rail covers the user's reported screenshot; tiles can follow).
2026-05-10 14:54:45 +00:00
riyadhafraa 51d6d17e0a Add image to illustrate shared file functionality
Adds an image file to the attached_assets directory.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0806b8c1-7df0-46f0-b955-ed43717d0dbb
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 14:39:35 +00:00
riyadhafraa 26ed7a195e notes(sent): multi-select + bulk delete on Sent tab (task #472)
- Add useBulkDeleteSentNotes hook with bounded concurrency (worker
  pool, cap = 6) over the existing per-id DELETE /notes/:id endpoint;
  returns {ok, failed} so the UI can render a precise toast. One cache
  invalidation at the end (notes + folders).
- notes.tsx: page-level selection state for the Sent tab only
  (sentSelectionMode, selectedSentIds, bulkDeleteOpen). Header
  Select/Cancel toggle, sticky bulk-action bar with count, select-all/
  clear, in-bar Cancel, Delete, and an AlertDialog confirmation. Auto
  exits selection mode when leaving the Sent tab; prunes selected ids
  to currently visible filteredSent on every change (search, refresh,
  successful delete).
- SentList: switched the outer card from a nested <button> to a
  div role="button" tabIndex=0 with Enter/Space handler so the
  selection-mode Checkbox is no longer a nested interactive control.
  Checkbox is aria-hidden / pointer-events-none inside selection mode.
- i18n: notes.bulk* keys (Select / Cancel / SelectAll / Clear / Count
  with _one plural / Delete / Confirm title+body / Result success /
  partial / failure) in both en.json and ar.json.
- New e2e: tests/notes-sent-bulk-delete.spec.mjs seeds 3 sent notes,
  selects 2, confirms the bulk delete, asserts the cards are gone and
  the surviving note remains (DB + UI).
- Out of scope (proposed as follow-ups #473/#474/#475): undo on bulk
  delete, multi-select on Inbox/Archived tabs, additional e2e cases.
2026-05-10 14:35:45 +00:00
riyadhafraa d7a386cf75 notes: bulk-select + bulk-delete on Sent tab (task #472)
- Add `useBulkDeleteSentNotes` hook that fans out parallel DELETE
  /notes/:id calls via Promise.allSettled and returns {ok, failed}
  so the UI can render a precise toast on partial failure. Caches
  invalidated once via `invalidateNotesAndFolders`.
- NotesPage: page-level `sentSelectionMode` + `selectedSentIds`
  Set, auto-reset when navigating away from the Sent tab.
- Header gets a Select / Cancel toggle, gated on
  `view === "sent" && filteredSent.length > 0`.
- New sticky bulk-action bar above the sent list with count,
  select-all/clear toggle, and Delete button.
- SentList accepts optional selectionMode/selectedIds/onToggle
  props; cards swap onClick (open → toggle) and show a Checkbox
  overlay + rose ring when checked.
- AlertDialog confirms before deleting; toast reports all-success,
  partial, or all-failed using pluralized i18n keys.
- New i18n keys under `notes.bulk*` in ar.json + en.json.

No server changes (existing DELETE /notes/:id is reused).

Out of scope (per task spec): undo, bulk-archive, bulk endpoint,
multi-select on Active/Received/Archived tabs.
2026-05-10 14:32:02 +00:00
riyadhafraa 2a792f4747 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: 916bf67a-f3d7-4451-8859-daa043acdb94
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 14:24:32 +00:00
riyadhafraa 019e10fa4a Task #471: Make the incoming-note popup a single uniform color
The popup card was rendering in three visible shades of the note's
color: a darker header band, the note color in the body, and another
darker action-bar band — caused by `bg-black/5 dark:bg-white/5`
overlays plus dividers on the header and footer rows. The user wanted
the entire card to be a single flat color matching the note.

Changes (artifacts/tx-os/src/components/notes/incoming-note-popup.tsx):
- Drag handle / header row: removed
  `border-b border-black/10 dark:border-white/10 bg-black/5
  dark:bg-white/5`. The row is now fully transparent and inherits the
  outer card's `colorBg(noteColor)`.
- Action row: removed `border-t border-black/10 dark:border-white/10
  bg-black/5 dark:bg-white/5`. Same treatment — fully transparent, no
  divider line.
- The outer card div remains the only source of background color, so
  every region now shows the exact same shade in both light and dark
  mode.

Out of scope (untouched): avatar bg/ring, ping pulse, default-color
behavior (`bg-background` still kicks in for the default color),
animation, drag/pointer handlers, button layout.

Validation:
- `pnpm --filter @workspace/tx-os exec tsc --noEmit` passes.
- Pre-existing `test` workflow failures (executive-meetings PDF,
  notes-share, groups-crud) are unchanged and out of scope.
2026-05-10 14:20:38 +00:00
riyadhafraa a9245bdf44 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: 73213a45-b0bc-473b-b0a8-8216a9afd747
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 14:19:56 +00:00
riyadhafraa 05eeb177ff Add a plus icon to indicate creating a new note
Update the notes composer component to include a Plus icon next to the "Take a note..." placeholder, visually indicating the action to create a new note.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 01f3a5db-a2fa-45cb-9733-81a92ebf3216
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 14:11:23 +00:00
riyadhafraa 6a541b5a21 Remove the outer color ring from incoming notes and square the corners
Update incoming note popup to remove the outer color ring and square its corners, aligning with user request for a cleaner visual appearance.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 99be2532-d204-4370-a1aa-cfc59e3abd4a
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 14:06:29 +00:00
riyadhafraa 671cedf51d Task #470: Remove popup color ring and square its corners
User asked to drop the colored ring around the floating new-note popup
(added in #468) and replace the rounded corners with square ones for a
cleaner, more formal notification look.

Changes (artifacts/tx-os/src/components/notes/incoming-note-popup.tsx):
- Removed `ring-4 ${ringClass}` from the outer card div so no colored
  outline appears around the popup.
- Replaced `rounded-2xl` with `rounded-none` so the card has square
  90° corners on all sides. `overflow-hidden` is preserved so the
  inner header / body / action bar still clip cleanly to the
  rectangle.
- Dropped the now-unused `colorRingStrong` import and its derived
  `ringClass` local. The shared helper itself stays in
  `notes-api.ts` in case future surfaces want it.
- Kept the neutral `shadow-2xl` so the card still separates from the
  background, and kept the avatar / ping color tinting from #468.

Validation:
- `pnpm --filter @workspace/tx-os exec tsc --noEmit` passes.
- No other functional changes (drag, dismiss, mark read, reply, open,
  checklist toggle, queue counter, RTL all untouched).
- Pre-existing `test` workflow failures (executive-meetings PDF,
  notes-share, groups-crud) are unchanged and out of scope.
2026-05-10 14:05:55 +00:00
riyadhafraa dd5a2a42bb 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: f6cf8c47-30da-4701-9de4-bebf837b4861
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 14:05:06 +00:00
riyadhafraa aaa705d843 Task #468: Match incoming-note popup colors to the note's own color
User reported that the floating "new note" popup had a fixed amber/
orange ring, avatar ring, ping pulse and shadow tint, which clashed
visually whenever the underlying note used a different color (e.g. a
pink note appeared inside an orange popup).

Changes:
- artifacts/tx-os/src/lib/notes-api.ts:
  - Extended the NOTE_COLORS array with four new fields per color:
    `ringStrong` (popup outer ring), `avatarBg` (avatar circle bg+text),
    `avatarRing` (2px avatar ring), and `ping` (animated pulse).
  - Each entry uses literal Tailwind utility strings so v4's build-time
    scanner picks them up — same pattern already used for `accent-*`.
  - Default + gray colors map to neutral slate variants so they stay
    legible on the white card surface.
  - Added small helpers: `colorRingStrong`, `colorAvatarBg`,
    `colorAvatarRing`, `colorPing` (each falls back to the default
    entry for unknown ids, matching `colorAccent`'s behavior).
- artifacts/tx-os/src/components/notes/incoming-note-popup.tsx:
  - Imported the new helpers and resolved them once per render from
    `current.color` (works for both note and reply popup variants since
    both expose the same `color` field on the payload).
  - Replaced every `amber-*` class on the outer ring, the avatar
    fallback bg/text, the avatar image ring, and the animated ping
    with the resolved color classes.
  - Dropped the colored shadow tint (`shadow-amber-500/30`) — the
    card now uses a neutral `shadow-2xl` so the only colored chrome
    is the ring matching the note.

Validation:
- `pnpm --filter @workspace/tx-os exec tsc --noEmit` passes.
- No remaining `amber` literal in the popup file (only an
  explanatory comment mentions the word).
- Pre-existing `test` workflow failures (executive-meetings PDF,
  notes-share, groups-crud) are unchanged and out of scope.
2026-05-10 14:01:21 +00:00
riyadhafraa 29334f2230 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: 9e2f56e3-f200-4fcf-965f-84d98898f64e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 13:59:26 +00:00
riyadhafraa b0647cb9b8 Task #466: Clean notes page from email-like 4-tab bar
User asked to remove the prominent 4-tab bar (My Notes / Inbox / Sent /
Archive) from the notes page because it made the page feel like an email
client, and they rarely use the share-between-users feature.

Changes (artifacts/tx-os/src/pages/notes.tsx):
- Deleted the inline-flex TabButton row that held the four view tabs.
- Page now opens directly on the unified "active" feed.
- Added an overflow DropdownMenu (⋯ MoreVertical icon) on the end of the
  controls row containing three items: Inbox (with unread badge), Sent,
  Archived. Each item calls setView() with the same TabId values, so all
  downstream branching (data fetching, filtering, composer visibility,
  folder rail) is unchanged.
- Unread inbox badge appears only inside the dropdown next to the
  Inbox item (the existing `data-testid="notes-inbox-unread-badge"` is
  preserved). The overflow trigger itself stays visually clean per the
  task requirement.
- Added a small "← My Notes" back button (testid notes-back-to-active)
  that appears only when view !== "active" so the user can return after
  drilling into Inbox/Sent/Archived without a visible tab bar.
- Removed the now-unused TabButton component definition.

Tests (artifacts/tx-os/tests/notes-inbox.spec.mjs):
- Replaced the two notes-tab-received / notes-tab-sent clicks with the
  open-dropdown-then-select-item sequence using the new overflow testids.

Validation:
- pnpm tsc --noEmit passes for @workspace/tx-os.
- Architect review: no severe issues; layout, a11y, RTL, z-index, and
  state-reset behavior all noted as sound.
- The `test` workflow has pre-existing failures (executive-meetings PDF,
  notes-share, groups-crud) that are explicitly out of scope per the
  task description and tracked by other open follow-up tasks.

No backend, schema, API, or i18n JSON changes were needed: the file
uses inline t() defaults, and `notes.tabs.received/sent/archived` keys
are reused inside the dropdown.
2026-05-10 13:51:11 +00:00
riyadhafraa ef1f23a11d Task #466: Clean notes page from email-like 4-tab bar
User asked to remove the prominent 4-tab bar (My Notes / Inbox / Sent /
Archive) from the notes page because it made the page feel like an email
client, and they rarely use the share-between-users feature.

Changes (artifacts/tx-os/src/pages/notes.tsx):
- Deleted the inline-flex TabButton row that held the four view tabs.
- Page now opens directly on the unified "active" feed.
- Added an overflow DropdownMenu (⋯ MoreVertical icon) on the end of the
  controls row containing three items: Inbox (with unread badge), Sent,
  Archived. Each item calls setView() with the same TabId values, so all
  downstream branching (data fetching, filtering, composer visibility,
  folder rail) is unchanged.
- Unread inbox badge now appears (a) as a small numeric dot on the
  overflow trigger when not currently in Inbox view, and (b) inside the
  dropdown next to the Inbox item — the existing
  `data-testid="notes-inbox-unread-badge"` is preserved.
- Added a small "← My Notes" back button (testid notes-back-to-active)
  that appears only when view !== "active" so the user can return after
  drilling into Inbox/Sent/Archived without a visible tab bar.
- Removed the now-unused TabButton component definition.

Tests (artifacts/tx-os/tests/notes-inbox.spec.mjs):
- Replaced the two notes-tab-received / notes-tab-sent clicks with the
  open-dropdown-then-select-item sequence using the new overflow testids.

Validation:
- pnpm tsc --noEmit passes for @workspace/tx-os.
- Architect review: no severe issues; layout, a11y, RTL, z-index, and
  state-reset behavior all noted as sound.
- The `test` workflow has pre-existing failures (executive-meetings PDF,
  notes-share, groups-crud) that are explicitly out of scope per the
  task description and tracked by other open follow-up tasks.

No backend, schema, API, or i18n JSON changes were needed: the file
uses inline t() defaults, and `notes.tabs.received/sent/archived` keys
are reused inside the dropdown.
2026-05-10 13:49:53 +00:00
riyadhafraa 28da25e2d6 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: 48ac1322-9e40-416e-936f-ec6837437656
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 13:46:22 +00:00
riyadhafraa f937362868 Task #463: @dnd-kit notes drag + reorder
- Replace HTML5+touch drag with @dnd-kit; MouseSensor (desktop, 8px)
  + TouchSensor (iPad, 200ms long-press) so input sources never overlap.
- Add sort_order column; ORDER BY asc(sortOrder), updatedAt desc.
- PATCH /notes/reorder: strict isPinned boolean check, bucket+permission
  scoped, all writes wrapped in db.transaction for atomicity.
- PATCH /notes/:id stamps sort_order = min-1 on folder/pin bucket change.
- Client useReorderNotes (PATCH) with optimistic cache update.
- handleDragEnd builds reorder payload from FULL bucket (owner notes
  or shared-folder bucket via ref), not the filtered subset, so hidden
  siblings under search/label filters keep their order.
- Drag guarded for view-only contexts: source must be owned OR in
  editable shared bucket; folder-drop additionally requires isOwn.
- SharedFolderView publishes its data.notes via bucketRef when viewer
  has edit permission, enabling correct reorder in shared folders.
- Layout fix at narrow viewport: rail stacks above notes
  (flex-col md:flex-row) so iPad portrait drag has proper bbox.
- Playwright tests: notes-folders.spec.mjs both desktop pointer drag
  and touch long-press drag pass (33s).
- OpenAPI codegen skipped: notes-api.ts is hand-written.
- Out of scope (pre-existing failures): executive-meetings reorder/font,
  notes-share PATCH 403/404, groups-crud rollback.
2026-05-10 13:17:00 +00:00
riyadhafraa daa4f6c038 Task #463: @dnd-kit notes drag + reorder
- Replace HTML5+touch drag with @dnd-kit (PointerSensor distance:8,
  TouchSensor delay:200/tol:8) matching home.tsx pattern.
- Add sort_order column to notes; ORDER BY asc(sortOrder), updatedAt desc.
- New PATCH /notes/reorder endpoint: strict isPinned boolean validation,
  bucket+permission scoped, all writes in db.transaction for atomicity.
- PATCH /notes/:id stamps sort_order = min-1 on folder/pin bucket change.
- Client useReorderNotes hook with optimistic cache update.
- handleDragEnd builds reorder payload from FULL bucket (owner notes or
  shared-folder bucket via ref), not the filtered/search subset, so
  hidden siblings retain stable order.
- SharedFolderView publishes its data.notes via bucketRef when viewer
  has edit permission, enabling correct reorder in shared folders.
- Layout fix at narrow viewport: rail stacks above notes
  (flex-col md:flex-row) so iPad portrait drag has proper bbox.
- Playwright tests: notes-folders.spec.mjs both desktop pointer drag
  and touch long-press drag pass (32s).
- OpenAPI codegen skipped: notes-api.ts is hand-written.
- Out of scope (pre-existing failures): executive-meetings reorder/font,
  notes-share PATCH 403/404, groups-crud rollback.
2026-05-10 13:10:01 +00:00
riyadhafraa 17c7b1bdaa 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: 335be058-07ac-465c-8e3e-57d24a996676
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 12:26:49 +00:00
riyadhafraa e103cbf5c1 Task #462: Fix iPad notification sounds (second attempt)
Original task: After #461 added an HTMLAudioElement playback path for
iOS, users still reported no sound on iPad. #462 identifies and fixes
three bugs in that earlier attempt.

Root causes addressed:
1. iOS unlocks ONE element per gesture, not all 8. The previous loop
   over 8 separate Audio elements left 7 of them gesture-locked.
2. `crossOrigin = "anonymous"` flipped same-origin .wav requests into
   CORS mode, causing silent load failure in Safari.
3. `el.volume = 0` is read-only on iOS, so the "silent priming" idea
   in #461 would have played 8 real chimes at once on first tap.

Implementation:
- Replaced the 8-element Map with a SINGLE shared HTMLAudioElement on
  iOS; rotate `.src` per play (iOS unlock is element-bound, not URL).
- Added `SILENT_WAV_DATA_URL`: a 60-byte inline silent WAV used to
  prime the element in `unlock()` — no network, no audible output.
- Removed `crossOrigin = "anonymous"`.
- `testPlay()` on iOS now calls `play()` directly (skipping the
  silent-prime) so the gesture-bound `play()` lands on the actual
  sound. Non-iOS keeps the original `unlock()`-then-`play()` order.
- `unlock()` is a no-op once primed; deliberately does NOT pause/reset
  the silent clip in its resolve handler — letting the ~30ms silence
  end naturally avoids racing with a `playIos()` `src` swap from a
  click that fires immediately after pointerdown.

Public API unchanged: play / testPlay / unlock / isUnlocked /
AUTOPLAY_BLOCKED_EVENT all preserve their signatures and observable
behavior. Test globals (__txosNotifPlayCount, __txosNotifLastSound,
__txosNotifCtxState) preserved.

Locale strings (`notifSettings.autoplayHintIos`) already focused on
media volume from #461 — no change needed.

Validation: `tsc --noEmit` clean. The pre-existing failures in the
api-server / test workflow (executive-meetings.ts) are unrelated and
explicitly out of scope per the plan. Manual iPad verification is
required to confirm the fix in production.

Files changed:
- artifacts/tx-os/src/lib/notification-sounds.ts (rewritten)
2026-05-10 12:08:45 +00:00
riyadhafraa 46de8fc72b 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: ed0671a5-6bc2-4d8b-9a5d-9ff1b0b70aab
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 12:01:57 +00:00
riyadhafraa fa529cd95a Improve sound playback on iOS by priming audio elements
Refactor the `NotificationPlayer` class to correctly handle audio playback initiation on iOS, ensuring that sounds play reliably after user gestures by properly managing the unlocked state based on successful priming of HTMLAudioElements.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: f541a3b8-ce77-4f70-9fa4-f1272f6e5c7a
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/mkNwf1z
Replit-Helium-Checkpoint-Created: true
2026-05-10 11:41:47 +00:00