Commit Graph

910 Commits

Author SHA1 Message Date
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
riyadhafraa ec29657bf5 Task #461: Fix notification sounds on iPad (route via media channel)
Problem: User reported zero sound on iPad — even the "Test sound"
button in notification settings produced silence. YouTube and other
videos worked fine on the same iPad.

Root cause: The app's notification player used the Web Audio API
(AudioContext + BufferSource). On iOS Safari (iPad/iPhone), Web Audio
plays through the **ringer channel**, which is silenced by:
  - The hardware silent switch (older iPads),
  - Control Center's mute icon,
  - Or simply having ringer volume at 0 (independent of media volume).
HTMLAudioElement (the API behind <video> and YouTube) plays through
the **media channel**, which is what users actually have turned up.

Fix in artifacts/tx-os/src/lib/notification-sounds.ts:
- Added an HTMLAudioElement playback engine alongside the existing
  Web Audio engine. Engine selection is one-time per session via the
  existing `detectIos()` helper (handles iPadOS-as-Mac masquerade).
- iOS path: lazy `new Audio(url)` per sound id with preload="auto",
  primed in `unlock()` by play()-then-pause() at volume 0 inside the
  user gesture so each element is permanently allowed to play later
  out-of-gesture (iOS requirement). Real `play()` resets currentTime
  and plays at full volume; rejection of the play promise dispatches
  the existing autoplay-blocked hint.
- Non-iOS path: unchanged Web Audio behavior preserved verbatim.
- All public API preserved: `unlock()`, `play()`, `testPlay()`,
  `isUnlocked()`, `AUTOPLAY_BLOCKED_EVENT`. No changes needed to
  use-audio-unlock, use-autoplay-hint, use-notifications-socket, or
  notification-settings.
- Throttling (3s burst-coalesce), bypass for testPlay, vibration,
  test-only observability globals, and isIos hint flag all preserved.
- `__txosNotifCtxState` test hook now reports "running"/"suspended"
  on iOS based on `unlocked` flag (no AudioContext exists there).

Locale updates (ar.json + en.json):
- `notifSettings.autoplayHintIos` rewritten: now tells users to raise
  the **media** volume (same one that controls YouTube) instead of
  the old "turn off Silent Mute" advice — matches the new playback
  channel.

Verification: tsc --noEmit clean. Pre-existing `test` workflow failure
on unrelated executive-meetings.ts type errors is out of scope.
Out of scope per task: no PWA, no push notifications, no settings UI
redesign, no new sound files. Manual iPad QA needed before merging.
2026-05-10 11:40:07 +00:00
riyadhafraa 72639c5faa 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: 5642e8a5-bf55-43e1-8353-b0c06a62d1df
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:37:46 +00:00
riyadhafraa 4650014631 Task #460: Hide iPad Safari URL bar on short pages too
Followup to #459. After that fix, Notes/Home no longer had
overflow-hidden, but the user reported the URL bar still didn't
collapse on Notes (when few notes), Services, or Notifications.

Root cause: iOS Safari only collapses its URL bar when the document
is actually scrollable. Services (small grid), Notifications (short
list), and Notes-when-empty all fit within the viewport on iPad, so
there's no scroll trigger. Executive Meetings worked only because
its schedule list always overflows.

Fix (touch-only, single CSS rule in index.css @layer base):
  @media (hover: none) and (pointer: coarse) {
    html, body { min-height: calc(100svh + 1px); }
  }

Why 100svh: it's the viewport-with-URL-bar-visible height, so the
+1px gap stays constant regardless of whether the bar is shown or
hidden — no oscillation. Why touch-only media query: keeps desktop
browsers from showing a stray scrollbar on short pages. Universal
fix — applies to every page automatically, no per-page edits.

Out of scope (per task): no PWA install meta, no per-page changes,
no styling changes. Pre-existing `test` workflow failure on
unrelated executive-meetings.ts type errors is not addressed here.
2026-05-10 11:33:44 +00:00
riyadhafraa d5c7ac1564 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: 9675917d-901b-4446-a6df-40d64388abc9
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:33:16 +00:00
riyadhafraa 920d224a68 Task #459: Hide iPad Safari URL bar across all apps (notes + home)
Problem: On iPad Safari, the executive-meetings app collapsed the URL
bar on scroll (native-app feel), but Notes and Home kept it visible
permanently because their root containers were not document-scrollable.
- notes.tsx used `h-screen ... overflow-hidden` with an internal
  `flex-1 min-h-0 overflow-y-auto` scroll region.
- home.tsx used `min-h-screen ... overflow-hidden`.
With no document-level scroll, iOS Safari has no trigger to hide the
URL bar.

Fix (quick-fix path the user picked, no PWA install meta):
- notes.tsx: root → `min-h-[100dvh] os-bg flex flex-col` (removed
  `overflow-hidden`, swapped `h-screen` → `min-h-[100dvh]`). Header now
  `sticky top-0 z-10` so it stays pinned. Inner body wrapper changed
  from `flex-1 min-h-0 overflow-y-auto flex flex-col` to
  `flex-1 flex flex-col` so the document (not an inner div) scrolls —
  giving Safari the trigger it needs.
- home.tsx: root → `min-h-[100dvh] os-bg flex flex-col relative`
  (removed `overflow-hidden`).

Why `100dvh`: dynamic viewport height sizes against the *visible*
area, so the layout reflows cleanly when the URL bar appears/hides.

Out of scope (matches task): no PWA meta tags, no other pages touched
(spot-checked others: chat/services/orders/notifications/admin already
use min-h-screen without overflow-hidden), no styling changes, executive-
meetings page untouched (it's the reference).

Verification: tsc --noEmit clean. Pre-existing test workflow failure on
unrelated executive-meetings.ts type errors is out of scope.
2026-05-10 11:22:31 +00:00
riyadhafraa 4317ccad8d 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: d1166ff2-0853-4ab9-be7c-362666c82334
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/OP4x0N4
Replit-Helium-Checkpoint-Created: true
2026-05-10 11:20:30 +00:00
riyadhafraa dba168b299 notes(#457): enlarge notes page header bar
- Outer header padding: px-4 py-3 → px-6 py-5 for a noticeably
  taller bar.
- Title row gap-3 → gap-4; back button p-2 → p-2.5 with size 24 icon
  (was 20).
- Title "Notes" text-lg → text-2xl; StickyNote icon size 20 → 28;
  inner gap-2 → gap-3.
- Search:
  - container max-w-md → max-w-xl, min-w-[180px] → min-w-[220px]
  - Input gets h-12 text-base + ps-11
  - Search icon size 16 → 20, offset bumped from 10 → 14 to match
    the new padding (RTL + LTR).
- Labels button: removed size="sm", now h-12 px-4 text-base with
  Tag icon size 20 (was 16) + me-2 (was me-1) for the larger label.
- Tab row spacing: mt-3 gap-2 → mt-4 gap-3; tab container padding
  p-1 → p-1.5.
- Tab icons size 14 → 18 with me-2 (was me-1).
- Inbox unread badge: text-[10px] / 18×18 → text-xs / 22×22 with
  px-1.5 so two-digit counts still fit.
- TabButton: px-3 py-1 text-sm → px-4 py-2 text-base.
- Header still uses flex-wrap so it folds gracefully on narrow
  screens; works in both RTL and LTR (search icon position uses
  the existing isRtl conditional).
- Out of scope (per task): no color/glass-panel changes, no behavior
  changes, no enlargement of the body content under the header,
  and no changes to the SharedFolderView header.
- Pre-existing executive-meetings.ts type errors unchanged.
2026-05-10 11:15:22 +00:00
riyadhafraa 4e00a20bda notes(#454): per-recipient view/edit folder sharing
- Schema: noteFolderShares.permission ('view'|'edit', default 'view').
  Project uses drizzle-kit push (no migration files); existing rows
  pick up the default automatically on next push.
- Server: resolveFolderEditAccess + emitFolderChanged helpers.
  - POST/PATCH/DELETE/checklist now allow folder editors.
  - Editors stamp notes with folder owner's userId; labels validated
    against owner; PATCH editors blocked from unfile (folderId=null)
    and cross-folder moves to non-matching owners.
  - emitFolderChanged fires for owner AND editor mutations across
    create / patch (old + new folder) / delete / checklist toggle.
  - PUT /shares accepts both legacy recipientUserIds and new
    recipients:[{userId,permission}]; diffs add/update/remove/perm-flip.
  - GET /shares, /shared-with-me, /shared-notes return permission /
    myPermission / readOnly.
  - Distinct socket events: note-folder-shared (added),
    note-folder-share-updated (permission flipped),
    note-folder-unshared (removed). Permission events carry the new
    permission so clients can react.
- Client:
  - notes-api types: FolderSharePermission, myPermission on
    SharedFolder/SharedFolderView, permission on FolderShareRecipient,
    readOnly:boolean on SharedFolderNote.
  - useUpdateFolderShares takes recipients[].
  - useCreate/Update/DeleteNote also invalidate ['note-folders'] so
    actor's shared-folder rail badge stays fresh.
  - FolderShareDialog: per-row checkbox + segmented View/Edit toggle.
  - SharedFolderView: editor mode mounts Composer + NoteCard with
    full edit/delete/archive affordances; Send hidden in editor
    context (Composer + NoteCard hideSend prop) since /send is
    owner-only.
  - folders-rail: per-folder permission badge.
  - socket: handlers for shared / share-updated / unshared all
    invalidate the right query keys for live UI flips.
  - Locales: shareDescriptionPerm, permissionView/Edit, canEdit
    (en + ar).
- Tests: new permission roundtrip test in notes-share.test.mjs covering
  view-rejects-write, edit-can-write, editor-cannot-unfile,
  editor-create-stamped-to-owner, downgrade/upgrade flips.
- Pre-existing executive-meetings.ts type errors are out of scope.
2026-05-10 11:11:45 +00:00
riyadhafraa d90893848d notes(#454): per-recipient view/edit folder sharing
- Schema: noteFolderShares.permission ('view'|'edit', default 'view').
- Server: resolveFolderEditAccess + emitFolderChanged helpers.
  - POST/PATCH/DELETE/checklist now allow folder editors.
  - Editors stamp notes with folder owner's userId; labels validated
    against owner; PATCH editors blocked from unfile (folderId=null)
    and cross-folder moves to non-matching owners.
  - emitFolderChanged fires for owner AND editor mutations across
    create / patch (old + new folder) / delete / checklist toggle.
  - PUT /shares accepts both legacy recipientUserIds and new
    recipients:[{userId,permission}]; diffs add/update/remove/perm-flip.
  - GET /shares, /shared-with-me, /shared-notes return permission /
    myPermission / readOnly.
- Client:
  - notes-api types: FolderSharePermission, myPermission on
    SharedFolder/SharedFolderView, permission on FolderShareRecipient,
    readOnly:boolean on SharedFolderNote.
  - useUpdateFolderShares takes recipients[].
  - useCreate/Update/DeleteNote also invalidate ['note-folders'] so
    actor's shared-folder rail badge stays fresh.
  - FolderShareDialog: per-row checkbox + segmented View/Edit toggle.
  - SharedFolderView: editor mode mounts Composer + NoteCard with
    full edit/delete/archive affordances; Send hidden in editor
    context (Composer + NoteCard hideSend prop) since /send is
    owner-only.
  - folders-rail: per-folder permission badge.
  - socket: note-folder-shared also invalidates ['notes'] +
    ['note-folders'] for fanout to owner / other editors.
  - Locales: shareDescriptionPerm, permissionView/Edit, canEdit
    (en + ar).
- Pre-existing executive-meetings.ts type errors are out of scope.
2026-05-10 11:07:15 +00:00
riyadhafraa 1aad708cb7 notes(#454): per-recipient view/edit folder sharing
- Schema: noteFolderShares.permission ('view'|'edit', default 'view').
- Server: resolveFolderEditAccess + emitFolderChanged helpers.
  - POST/PATCH/DELETE/checklist now allow folder editors.
  - Editors stamp notes with folder owner's userId; labels validated
    against owner; PATCH editors blocked from unfile (folderId=null)
    and cross-folder moves to non-matching owners.
  - emitFolderChanged fires for owner AND editor mutations across
    create / patch (old + new folder) / delete / checklist toggle.
  - PUT /shares accepts both legacy recipientUserIds and new
    recipients:[{userId,permission}]; diffs add/update/remove/perm-flip.
  - GET /shares, /shared-with-me, /shared-notes return permission /
    myPermission / readOnly.
- Client:
  - notes-api types: FolderSharePermission, myPermission on
    SharedFolder/SharedFolderView, permission on FolderShareRecipient,
    readOnly:boolean on SharedFolderNote.
  - useUpdateFolderShares takes recipients[].
  - useCreate/Update/DeleteNote also invalidate ['note-folders'] so
    actor's shared-folder rail badge stays fresh.
  - FolderShareDialog: per-row checkbox + segmented View/Edit toggle.
  - SharedFolderView: editor mode mounts Composer + NoteCard with
    full edit/delete/archive affordances; Send hidden in editor
    context (Composer + NoteCard hideSend prop) since /send is
    owner-only.
  - folders-rail: per-folder permission badge.
  - socket: note-folder-shared also invalidates ['notes'] +
    ['note-folders'] for fanout to owner / other editors.
  - Locales: shareDescriptionPerm, permissionView/Edit, canEdit
    (en + ar).
- Pre-existing executive-meetings.ts type errors are out of scope.
2026-05-10 10:16:05 +00:00
riyadhafraa e52c27f377 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: bb2ad427-1c89-44b4-8bc2-98d0822eb99e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/R0O2qN7
Replit-Helium-Checkpoint-Created: true
2026-05-10 09:58:52 +00:00
riyadhafraa 0e384b6898 Improve how shared folders appear in the navigation sidebar
Hide the "Shared with me" header in certain views and add tooltips to indicate folder owners.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 26650ab1-4e19-4da3-8ed7-e3535055e62a
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/R0O2qN7
Replit-Helium-Checkpoint-Created: true
2026-05-10 09:54:00 +00:00
riyadhafraa 5868507bcc Notes: show "Shared with me" folders on the default screen too
Task #453. Recipient (e.g. yas) couldn't see folders that an owner
(e.g. admin) had shared with them — the server returned them
correctly via /note-folders/shared-with-me, but the rail's
"Shared with me" section was gated on `mode === "rail"`. On the
default Notes screen (My Notes tab + "All notes" selection) the
rail is mounted in `chips-only` mode, so the section was never
rendered. Most recipients only ever saw the default screen, so
shared folders were effectively invisible.

Fix: drop the `mode === "rail"` gate in folders-rail.tsx so the
shared-with-me section renders in both modes. The section already
uses `contents md:block` (so chips inline into the mobile horizontal
strip), and its "Shared with me" label is already `hidden md:block`,
so the rendering stays compact on mobile and labelled on desktop in
both modes.

No backend changes; the existing socket invalidations on
`note-folder-shared` / `note-folder-unshared` already keep the
list fresh in realtime.

Out of scope:
- FoldersBrowser (the grid/list of own folders in the main content
  area) intentionally not extended to also list shared folders;
  the rail entry is sufficient and matches the task's "as a chip
  alongside the own-folder chips" outcome on mobile and
  "Shared with me" sidebar section on desktop.
- No changes to permissions, notifications, or per-note sharing.
- Notes top-bar freeze (Task #452) is untouched.
2026-05-10 09:53:06 +00:00
riyadhafraa 990099113a 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: e240eaae-4a48-4196-b4ea-fd3ddfee0154
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/R0O2qN7
Replit-Helium-Checkpoint-Created: true
2026-05-10 09:51:34 +00:00
riyadhafraa 5753aa176d Notes: lift top bar out of the scroll area so it never scrolls off
Task #452. After #451, the page wrapper was h-screen + overflow-y-auto
with the header still using `sticky top-0 z-20`. Users kept reporting
that the bar slides up out of view on scroll — the well-known
`position: sticky` failure mode when the sticky child is a direct flex
item of the same element that is also the scroll container.

Restructured artifacts/tx-os/src/pages/notes.tsx so the bar is a
sibling of the scroller, not inside it:

- Page wrapper: `h-screen os-bg flex flex-col overflow-hidden`
  (was `... overflow-y-auto`). The wrapper itself never scrolls.
- Header div: dropped `sticky top-0 z-20`, added `shrink-0`. As a
  non-shrinking flex child of the column wrapper it stays pinned at
  the top by layout, with no sticky math involved.
- Wrapped the composer block and the main content row
  (`flex-1 px-4 py-6 max-w-6xl ... flex gap-6` plus its inner
  `flex-1 min-w-0` column) inside a single new scrolling region:
  `<div className="flex-1 min-h-0 overflow-y-auto flex flex-col">`.
  `min-h-0` is required so the flex child can shrink below its
  content height and let `overflow-y-auto` actually engage.
- Dialogs (Edit, Labels, Send, FolderShare, Thread) remain siblings
  of the scroller, so popovers/modals are unaffected.

No changes to index.css, header styling, composer styling, folder
layout, or any other page. RTL/LTR behavior unchanged.

Out of scope: pre-existing test failures (executive-meetings,
notes-share, service-orders) are unrelated to this layout fix.
2026-05-10 09:44:44 +00:00
riyadhafraa d76f8861f4 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: 36333983-5fe8-4e48-ba69-2564fe031c2d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/R0O2qN7
Replit-Helium-Checkpoint-Created: true
2026-05-10 09:42:53 +00:00
riyadhafraa 5f7bb77985 Task #451: pin Notes header via an internal scroll container
Despite Tasks #448–#450 (sticky header + fixing .os-bg / html,body
overflow rules), the user kept reporting the bar disappears on
scroll. position: sticky is sensitive — the browser was picking
the wrong scroll container in this layout.

Robust fix: stop relying on document/body for scrolling on the
Notes page. Changed the page wrapper className in
artifacts/tx-os/src/pages/notes.tsx from
  min-h-screen os-bg flex flex-col
to
  h-screen os-bg flex flex-col overflow-y-auto

Now the Notes wrapper is a known scroll container that's exactly
one viewport tall. The sticky header inside it pins reliably to
the top edge of that container (== top of the viewport). Composer
flush-placement (Task #448), index.css overflow-x:clip on
html,body (Task #450), and other pages using .os-bg are all
untouched.

Single-line change, no other refactors.
2026-05-10 09:35:21 +00:00
riyadhafraa 479cdd9d3d 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: 47c2eaeb-2f12-47cb-a6f9-36e4f698c4be
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/R0O2qN7
Replit-Helium-Checkpoint-Created: true
2026-05-10 09:35:06 +00:00
riyadhafraa a8eeae56fd Task #450: switch html/body horizontal clip from hidden to clip
After Task #449 the sticky Notes header was still scrolling away.
Root cause: `overflow-x: hidden` on `html, body` makes both
elements scroll containers. Body sized itself to its content
(no internal scroll happens), so a `position: sticky` descendant
got scoped to a non-scrolling container and rode off-screen with
the page.

`overflow-x: clip` clips overflowing content the same visual way
`hidden` does, but it does NOT establish a scroll container, so
sticky descendants continue to resolve against the viewport and
pin correctly.

Changed only `artifacts/tx-os/src/index.css` (the `html, body`
rule added in #449). All other rules untouched. `overflow: clip`
is supported in Chrome 90+, Firefox 81+, Safari 16+.
2026-05-10 09:29:05 +00:00
riyadhafraa a5472e32fc 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: 975562de-ea93-44bd-9538-63981609b057
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 09:28:51 +00:00
riyadhafraa 09cc87d510 Task #449: make Notes header truly stick by fixing scroll-context bug
After Task #448 switched the Notes header to `sticky top-0`, it
still scrolled away with the page. Root cause was in
`artifacts/tx-os/src/index.css`: the shared `.os-bg` class set
`overflow-x: hidden`, which per CSS spec turns the element into a
scroll container. Since the Notes page wrapper carries `os-bg` and
also has `min-h-screen`, the wrapper expanded to fit content
(never scrolled internally) while the document body scrolled —
which meant the sticky header was scoped to a non-scrolling
container and rode along with the page.

Fix:
- Removed `overflow-x: hidden` from `.os-bg`.
- Added `html, body { overflow-x: hidden; }` inside the existing
  `@layer base` block so the horizontal-clip protection still
  exists at the document level (which does not break sticky in
  descendants).

No other changes. Notes page header now actually pins to the top
during scroll across all tabs / folder views, and other pages that
use `.os-bg` (home, services, executive-meetings, etc.) keep their
horizontal-overflow protection via the body-level rule.
2026-05-10 08:56:17 +00:00
riyadhafraa 0f049445f2 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: f79f0961-2115-4786-8496-593bcb2ea869
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 08:55:52 +00:00
riyadhafraa 6ed0fea0e9 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:34:00 +00:00