Commit Graph

835 Commits

Author SHA1 Message Date
riyadhafraa 6878cdfe7c Task #511: Fully remove Chat feature
Destructive removal per user confirmation ("حذف نهائي ما يرجع").

Removed:
- API: routes/conversations.ts, schema/conversations.ts, all chat
  socket handlers in src/index.ts, /admin/users/:id/dependents/
  conversations+messages endpoints, conversation/message dependency
  counts in users/stats routes.
- Web: pages/chat.tsx, /chat route, dock chat filter, MessageSquare
  icon and messages StatCard on home, all chat-related UI in
  notifications + admin (dependency badges, delete-dialog rows,
  UserDependentConversations/Messages sections, count map keys).
- Locales: nav.chat, home.stats.messages, full chat.* block,
  admin.deleteUser conv/msgCount, admin.users.counts.conv/msg,
  admin.audit.unit.conversation_*/message_*, admin.dependents.user*.
- OpenAPI spec: tags, all /conversations/* paths, conv/msg dependent
  paths, related schemas (ConversationWithDetails, MessageWithSender,
  UserDependentConversation/MessageItem+Page, etc.), UserProfile and
  UserDeletionConflict conv/msg fields, HomeStats.unreadMessages.
  Regenerated client via orval.
- Database: dropped message_reads, messages,
  conversation_participants, conversations (CASCADE); deleted
  notifications with related_type='conversation' or type='chat';
  deleted apps row with slug='chat'; ran drizzle push-force.
- Seed: removed chat:access permission + user-role assignment +
  seeded chat app entry from scripts/src/seed.ts.
- Tests: deleted conversations-leave.test.mjs; cleaned chat refs from
  list-dependency-counts, delete-force-warnings, audit-log-coverage,
  and admin-inline-dependency-counts (e2e) — replaced chat dependents
  with note dependents where needed for force-delete coverage.

Notes preserved: notes.tsx noConversationsYet/conversationWith refer
to NOTE THREADS (not chat) and were intentionally NOT touched.

executive-meetings.ts not modified per replit.md restriction.
Pre-existing flaky test failures in executive-meetings/group/etc
suites remain unrelated to this task.
2026-05-12 10:54:40 +00:00
riyadhafraa 84398de390 Task #511: Fully remove Chat feature
Destructive removal per user confirmation ("حذف نهائي ما يرجع").

Removed:
- API: routes/conversations.ts, schema/conversations.ts, all chat
  socket handlers in src/index.ts, /admin/users/:id/dependents/
  conversations+messages endpoints, conversation/message dependency
  counts in users/stats routes.
- Web: pages/chat.tsx, /chat route, dock chat filter, MessageSquare
  icon and messages StatCard on home, all chat-related UI in
  notifications + admin (dependency badges, delete-dialog rows,
  UserDependentConversations/Messages sections, count map keys).
- Locales: nav.chat, home.stats.messages, full chat.* block,
  admin.deleteUser conv/msgCount, admin.users.counts.conv/msg,
  admin.audit.unit.conversation_*/message_*, admin.dependents.user*.
- OpenAPI spec: tags, all /conversations/* paths, conv/msg dependent
  paths, related schemas (ConversationWithDetails, MessageWithSender,
  UserDependentConversation/MessageItem+Page, etc.), UserProfile and
  UserDeletionConflict conv/msg fields, HomeStats.unreadMessages.
  Regenerated client via orval.
- Database: dropped message_reads, messages,
  conversation_participants, conversations (CASCADE); deleted
  notifications with related_type='conversation' or type='chat';
  deleted apps row with slug='chat'; ran drizzle push-force.
- Seed: removed chat:access permission + user-role assignment +
  seeded chat app entry from scripts/src/seed.ts.
- Tests: deleted conversations-leave.test.mjs; cleaned chat refs from
  list-dependency-counts, delete-force-warnings, audit-log-coverage,
  and admin-inline-dependency-counts (e2e) — replaced chat dependents
  with note dependents where needed for force-delete coverage.

Notes preserved: notes.tsx noConversationsYet/conversationWith refer
to NOTE THREADS (not chat) and were intentionally NOT touched.

executive-meetings.ts not modified per replit.md restriction.
Pre-existing flaky test failures in executive-meetings/group/etc
suites remain unrelated to this task.
2026-05-12 10:51:31 +00:00
riyadhafraa e4ebaffe73 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: 9ff83fa0-239c-4735-b714-1b1bbb3dcc1d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv
Replit-Helium-Checkpoint-Created: true
2026-05-12 10:29:59 +00:00
riyadhafraa 992f98418c Add ability to archive multiple incoming notes at once
Introduces a bulk archive feature for the received notes tab, including API integration, UI selection state, confirmation dialogs, and internationalization support.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7c13c05c-e8c2-461e-a5dc-94bbc8e114ac
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv
Replit-Helium-Checkpoint-Created: true
2026-05-12 10:25:41 +00:00
riyadhafraa 81d27ed8ec 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: 587d9c0d-0c50-4865-ba7e-34fb698d3584
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv
Replit-Helium-Checkpoint-Created: true
2026-05-12 10:22:30 +00:00
riyadhafraa 207d2ad52b Improve Arabic text display by fixing clipped dots on characters
Add a global CSS rule to increase line-height for truncated Arabic text, ensuring diacritics are fully visible.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 8f7b38c9-dbac-45e9-86d9-0cb4f4a5d011
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv
Replit-Helium-Checkpoint-Created: true
2026-05-12 09:47:05 +00:00
riyadhafraa e4b5ece2e4 Enhance the app editing interface with improved input hints
Add helper text and inline hints to the slug and route input fields within the app editing form to clarify their purpose and expected format.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 18fca138-6c4d-4f2b-9859-dcd578b8df5c
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv
Replit-Helium-Checkpoint-Created: true
2026-05-12 09:36:50 +00:00
riyadhafraa d5dd3acf9f Task #509: Redesign Edit App modal + verify responsive sizing
User asked for the Edit App modal to match the new professional Edit
Service design and confirmed responsiveness for iPad / mobile.

Changes — admin.tsx only:

1. New shell matching Edit Service modal:
   - bg-slate-950/50 backdrop-blur-md overlay, click-outside-to-close,
     fade + zoom-in animations.
   - White rounded-2xl container, max-w-2xl (wider than Service modal
     because of the additional fields + permissions list), max-h-[90vh],
     flex column with sticky-feeling header / footer.
   - Header tile is now LIVE — its gradient color comes from the
     editing app's color field, and the icon comes from the app's
     iconName via a new resolveAppIcon() helper. So the header
     previews the app as you edit.
   - X close button in header.

2. Form layout:
   - Names row (sm:grid-cols-2): nameAr (RTL, ع badge) | nameEn (LTR,
     EN badge).
   - Identifier row (sm:grid-cols-2): slug | route, both font-mono.
   - Visuals row (sm:grid-cols-2):
     - Icon picker: 40x40 preview tile + text input. Tile shows the
       resolved Lucide icon, or a dashed HelpCircle when the name
       doesn't resolve. Hint text under the field.
     - Color picker: clickable color swatch wrapping a hidden native
       <input type=color> + hex text input. Both stay in sync.
   - Permissions wrapped in a bordered card with its own header strip.

3. Footer: Cancel (outline) + Save (gradient indigo→blue, shadow),
   each flex-1 h-10 rounded-lg.

4. New helper resolveAppIcon() above the imports — does a safe lookup
   into the lucide-react namespace and returns null when the name is
   invalid. Mirrors the pattern already used in pages/home.tsx.

Responsiveness verified by reasoning over breakpoints:
- Mobile (≤414px): sm: never kicks in, so all rows stack into one
  column; outer p-4 keeps gutters; max-h-[90vh] + scroll handles tall
  permissions.
- iPad portrait (768px) and up: sm:grid-cols-2 activates, two-column
  layout fits comfortably inside max-w-2xl (672px).
- Desktop: same max-w-2xl cap looks well-proportioned, not cramped.
- Edit Service modal already uses the same primitives, no changes
  needed.

No behavioral changes — all state wiring (editingApp, handleSaveApp,
permission flows, history sections) is unchanged. Pure presentational
refactor.

Code review: skipped — presentational refactor with no logic / data
flow changes.
Follow-ups: none proposed — request fully addressed; the other admin
modals are deliberately out of scope per the plan.
2026-05-12 09:35:47 +00:00
riyadhafraa 2ee744a003 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: e8c0dc66-20b5-41e9-a190-072f46c5078e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv
Replit-Helium-Checkpoint-Created: true
2026-05-12 09:33:57 +00:00
riyadhafraa c6fa183203 Redesign service editing window for a professional look and feel
Refactors the "Edit Service" modal UI in `admin.tsx` to improve visual hierarchy, user experience, and overall professionalism, including styling changes to the header, input fields, image uploader, and availability toggle.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 28ee2705-c2ec-476e-aa4f-cc6c7cd5624e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/O07Jndv
Replit-Helium-Checkpoint-Created: true
2026-05-12 09:27:57 +00:00
riyadhafraa a565d47027 Task #508: Remove unused السعر (Price) field from admin Services UI
User requested removal of the price field from the admin Services edit
form ("بالنسبه الى الاسعار أحذفها لايوجد اسعار") because there are no
prices in the product.

Changes — frontend only, all in tx-os:
- artifacts/tx-os/src/pages/admin.tsx
  - Drop `price: string` from ServiceForm type.
  - Drop `price: "0.00"` from emptyServiceForm.
  - Remove the price entry from the edit dialog's field array
    (no more السعر input).
  - Remove the `<div>{service.price}</div>` from the services list.
  - Remove the `price` line from the edit-load mapper.
- artifacts/tx-os/src/locales/ar.json + en.json
  - Drop the now-unused `servicePrice` key.

Left intact:
- The generic `"price"` key in locales (line 104) — kept conservatively
  in case anything outside this scope expects it, though ripgrep shows
  no current usage.
- The `services.price` DB column and api-server code — unused in api
  src; only test fixtures insert a placeholder. Removing would require
  a migration and break tests for zero benefit.
- The user-facing services page — never displayed price.

Verification:
- ripgrep confirms zero `price` / `servicePrice` references remain in
  artifacts/tx-os/src/pages/admin.tsx and the locales (other than the
  generic shared key).
- Vite HMR applied cleanly with no TS errors.
- Pre-existing test failures (executive-meetings, service-orders) are
  unrelated and already tracked as separate project tasks.

Code review: skipped — change is a 5-line mechanical removal of dead
UI with no logic or data-flow implications.

Follow-ups: none proposed — this is a self-contained, complete fix.
2026-05-12 09:16:52 +00:00
riyadhafraa a4f89405e7 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: 76d5058d-35b6-4d73-912b-cbac4c490163
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/ZXx6nVc
Replit-Helium-Checkpoint-Created: true
2026-05-12 09:15:17 +00:00
riyadhafraa b87f9224a7 Task #507: Stop .os-bg from overriding sticky on its direct children
User report: after #505 the admin "لوحة التحكم" header looked pinned
at the top of the page, but disappeared as soon as the user scrolled
down. Same bug silently affected every page wrapped in `.os-bg`.

Root cause (artifacts/tx-os/src/index.css, lines 190-193):

  .os-bg > * { position: relative; z-index: 1; }

This rule was meant to lift page content above the decorative
gradient blobs (`.os-bg::before/::after`, position:fixed z-index:0).
But by setting `position: relative` on every direct child it also
overrode any `position: sticky` declared via Tailwind utilities on
those children — equal-specificity class selectors, with index.css
loaded after Tailwind utilities, so .os-bg won source-order. The
"sticky" headers therefore behaved as plain in-flow elements and
scrolled away with the rest of the content.

Fix (one CSS rule):
- Replace `position: relative; z-index: 1` with `isolation: isolate`.
- `isolation: isolate` creates a new stacking context (so children
  still paint above the z-index:0 blobs) without forcing them to
  position:relative — sticky/fixed children now work as written.

No markup changes anywhere. Followed Option A in the task plan
exactly. Implication: every sticky header across the app (admin,
home, chat, services, notifications) is now actually pinned on
scroll. Decorative blobs continue to sit behind page content
because the new stacking context still paints over the parent's
z-index 0 background layer.

No tests were modified; existing tx-os tests are presentation-
agnostic and unaffected.
2026-05-12 09:06:24 +00:00
riyadhafraa 3d346ed1b1 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: 40ab6575-a938-4675-9bc4-46b7c074bff3
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/ZXx6nVc
Replit-Helium-Checkpoint-Created: true
2026-05-12 09:05:51 +00:00
riyadhafraa 9a07018314 Task #505: Pin the admin "لوحة التحكم" top bar so it stays frozen on iPad
User report: in the admin dashboard on iPad, the top bar (back arrow,
gear icon, "لوحة التحكم" title, mobile menu button) felt like it
detached from the top during scroll — content showed through it and
it didn't look pinned.

Root cause (artifacts/tx-os/src/pages/admin.tsx, line 1148):
the header was `glass-panel ... sticky top-0 z-10`. The `glass-panel`
background is rgba(255,255,255,0.7) — semi-transparent — and z-10 is
low enough that floating elements on the page (os-bg blobs, sidebar
backdrop) bled across it. The sticky positioning itself was correct;
the perception of "not pinned" came from the see-through background.

Fix (single-file CSS-only change, exactly as planned):
- Drop `glass-panel`, replace with inline styles for full control.
- Background: rgba(255,255,255,0.96) — visually opaque so scrolled
  content no longer shows through.
- Keep blur(16px) saturate(160%) (with -webkit- prefix) so the bar
  still has the same soft glass aesthetic.
- z-30 instead of z-10 so nothing in the page overlays it. Modals
  use z-50 so they still cover the bar correctly.
- Add boxShadow 0 4px 20px rgba(15,23,42,0.06) so the bar visibly
  floats above content once scrolling starts.
- Kept `sticky top-0` (not converted to fixed) so the desktop
  sidebar's `top-[73px]` offset still aligns with the bar's bottom.
- Header height unchanged (px-4 py-4 + same children).

No layout changes. No other top bars touched (out of scope).
Existing tx-os tests are unaffected (no logic, only presentation).
2026-05-12 09:01:37 +00:00
riyadhafraa e2fb4f13fb 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: edd211fc-c18b-4623-b908-1da0295e493b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/ZXx6nVc
Replit-Helium-Checkpoint-Created: true
2026-05-12 09:00:51 +00:00
riyadhafraa be75334b88 Restore original open graph image
Revert changes to the open graph image file to restore its original state.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b9cb0273-9286-4060-8299-b101d36ad5aa
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/ZXx6nVc
Replit-Helium-Checkpoint-Created: true
2026-05-12 08:48:11 +00:00
riyadhafraa f180c99f52 Task #503: Stop home-screen Arabic labels from clipping letter dots on iPad
User report: in the Tx OS launcher, the dot above ج in "الاجتماعات"
(and similar diacritics on other tile labels) was being sliced off on
iPad — a visible visual-quality bug on the most-used screen.

Root cause (artifacts/tx-os/src/pages/home.tsx, AppIconContent):
the label span used `leading-tight` (line-height 1.25) + `truncate`
(overflow: hidden). At 11px the line-box was 13.75px tall — too short
for Arabic ascenders + diacritics — and overflow:hidden then clipped
the parts that overshot. iPad/WebKit rasterizes these faces slightly
taller than desktop, so the clip is most visible there.

Fix (single-file CSS-only change):
- Replace `leading-tight` with `lineHeight: 1.6` for plenty of room
  above the baseline.
- Add `paddingTop: 2` as cheap insurance for any remaining overshoot.
- Keep ellipsis truncation behaviour explicitly via inline style
  (overflow:hidden + textOverflow:ellipsis + whiteSpace:nowrap), so
  long Arabic names still ellipsize at the 78px max-width.
- Add `block` so max-width applies cleanly.

No change to icon tile size, grid layout, badges, or any other
markup. Acceptance items in the task plan all hold:
- Letter dots are no longer clipped.
- Long names still truncate with an ellipsis at 78px.
- Desktop layout is visually unchanged.

Deviation from plan: chose the inline-style escape hatch over
line-clamp-1, because line-clamp uses `display: -webkit-box` which
can interact with `max-width` differently across browsers, and the
explicit overflow/ellipsis form is the closest behavioural match to
the previous `truncate`.
2026-05-12 08:47:16 +00:00
riyadhafraa a720afd2b9 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: 0868d35f-faef-49ee-b520-ffa7e08174c1
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/4ugHzxo
Replit-Helium-Checkpoint-Created: true
2026-05-12 08:45:58 +00:00
riyadhafraa 641e26f0ad Update PDF generation to improve font compatibility and add legacy markers
Map PDF fonts to Noto Sans Arabic and Noto Naskh Arabic for consistent rendering, and append compatibility markers for legacy PDF readers.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 53f2ff6f-9fb8-40af-be07-1ab6041ecc58
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/4ugHzxo
Replit-Helium-Checkpoint-Created: true
2026-05-12 06:39:44 +00:00
riyadhafraa 0489f6b1dc Add title to PDF metadata and improve PDF filename handling
Adds a title tag to the HTML output for PDF generation and refactors the Content-Disposition header to include both a stable ASCII filename and a localized, human-readable filename for PDF downloads.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7b3c6ef0-56bb-48e7-8f67-40892dcf240d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/4ugHzxo
Replit-Helium-Checkpoint-Created: true
2026-05-11 20:59:42 +00:00
riyadhafraa 6dc016261c Improve PDF rendering and access control for notes
Address issues with Arabic text rendering in PDFs and adjust access control for note sharing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 616f5bca-46d2-4c5f-acc3-689cf0e525e0
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/4ugHzxo
Replit-Helium-Checkpoint-Created: true
2026-05-11 18:49:10 +00:00
riyadhafraa e81ba3a4b9 Add visual evidence of correct Arabic text rendering in PDFs
Add a PNG image to artifact metadata demonstrating correct Arabic text rendering in a generated PDF, resolving concerns about character shaping and order.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 16fbddba-fcd7-4fef-9cc4-d5c46409d005
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/4ugHzxo
Replit-Helium-Checkpoint-Created: true
2026-05-11 18:30:03 +00:00
riyadhafraa 95522dacbe Task #501: Default new notes to yellow
The notes composer now starts with the yellow swatch pre-selected
instead of the neutral "default" color, so a freshly created note
becomes a yellow sticky by default. The user can still pick any
other color from the swatch picker before saving, and that choice
is honored. After a successful save (or send), the composer resets
back to yellow rather than "default", so the next note in the same
session is also yellow unless the user changes it.

Changes:
- artifacts/tx-os/src/pages/notes.tsx
  - Composer initial color state: useState("default") -> useState("yellow")
  - Composer reset() helper: setColor("default") -> setColor("yellow")

Out of scope (per task spec):
- No backend changes. The API's data.color = "default" fallback for
  notes created via other paths (shared-folder stamping, incoming-
  note flows) is intentionally untouched.
- No changes to existing notes already saved in the database.
- No new "default color" user setting.
- NOTE_COLORS palette and color-picker UI unchanged.

Verification:
- `pnpm exec tsc --noEmit` passes clean.
- "yellow" is a valid id in NOTE_COLORS (src/lib/notes-api.ts:801),
  so colorBg/colorAccent/colorMeta resolve correctly and the swatch
  shows pre-selected.
- Color-picker onChange path unchanged, so user-picked colors still
  override the new yellow default before save.
2026-05-11 15:48:35 +00:00
riyadhafraa 5efaac8c2c 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: a151193c-67e5-4794-8855-df49edd82981
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/4ugHzxo
Replit-Helium-Checkpoint-Created: true
2026-05-11 15:47:37 +00:00
riyadhafraa d119480684 Task #499: fix Quick Actions dialog X overlap + iPad long-press drag feedback
Two scoped UX fixes on Executive Meetings schedule, both confined to
artifacts/tx-os/src/pages/executive-meetings.tsx (shared
src/components/ui/dialog.tsx untouched per task spec).

1) Quick Actions dialog header overlap
   - Radix DialogContent renders the Close (X) at physical
     `right-4 top-4` regardless of `dir`. In AR (RTL) the
     `text-right`-aligned title "إجراءات سريعة" pinned to the same
     edge and visibly overlapped the X.
   - Fix: added `pr-8` to the per-row Quick Actions DialogHeader so
     the title's right edge always clears the close button. Works
     for AR (overlap eliminated) and EN (just adds trailing slack).

2) iPad long-press drag feedback
   - dnd-kit's TouchSensor has a 200ms activation delay; previously
     the row gave no cue during the hold window so users lifted
     early thinking the schedule didn't register.
   - Fix: added `isPressing` state toggled via capture-phase
     onPointerDown/Up/Cancel handlers on the row tr, gated on
     `pointerType === "touch"` && `effectiveDragEnabled`. Capture
     phase chosen so handlers don't collide with safeRowDragListeners
     (which spreads dnd-kit's bubble-phase onPointerDown).
   - Visual cue: subtle `inset 0 0 0 2px hexToRgba(highlightColor,
     0.45)` ring composed AFTER quickOpen/selection/current rings so
     it never outranks a real selection state. Per spec, the cue
     appears during the 200ms hold AND continues throughout the
     active drag (we deliberately do NOT clear isPressing on the
     rising edge of isDragging). Two defensive useEffects clear
     `isPressing`: one when drag becomes disabled (capability flip /
     edit-mode toggle), one tied to the FALLING edge of isDragging
     (drag just ended) as a belt-and-suspenders guard against stale
     visual state when dnd-kit absorbs the touch sequence and our
     row's own pointerup never fires.
   - data-pressing="true" attribute exposed on tr for tests.
   - TouchSensor delay (200ms) and tolerance (8px) unchanged.

Tests added:
 - executive-meetings-quick-actions-dialog-layout.spec.mjs: asserts
   close button + title bounding boxes don't overlap in AR + EN.
 - executive-meetings-row-touch-press-feedback.spec.mjs: uses
   hasTouch+isMobile + CDP Input.dispatchTouchEvent, asserts
   data-pressing flips true on touchstart, clears on touchend, and
   tap-to-open quick-actions dialog still works for short taps;
   second test verifies the cue persists past the 200ms window.

Verification: pnpm tsc --noEmit clean. Pre-existing `test` workflow
failures (rotate-content/font-settings/notes-share) are unrelated to
this change.
2026-05-11 15:43:30 +00:00
riyadhafraa 9b0dbb037c Task #499: fix Quick Actions dialog X overlap + iPad long-press drag feedback
Two scoped UX fixes on Executive Meetings schedule, both confined to
artifacts/tx-os/src/pages/executive-meetings.tsx (shared
src/components/ui/dialog.tsx untouched per task spec).

1) Quick Actions dialog header overlap
   - Radix DialogContent renders the Close (X) at physical
     `right-4 top-4` regardless of `dir`. In AR (RTL) the
     `text-right`-aligned title "إجراءات سريعة" pinned to the same
     edge and visibly overlapped the X.
   - Fix: added `pr-8` to the per-row Quick Actions DialogHeader so
     the title's right edge always clears the close button. Works
     for AR (overlap eliminated) and EN (just adds trailing slack).

2) iPad long-press drag feedback
   - dnd-kit's TouchSensor has a 200ms activation delay; previously
     the row gave no cue during the hold window so users lifted
     early thinking the schedule didn't register.
   - Fix: added `isPressing` state toggled via capture-phase
     onPointerDown/Up/Cancel handlers on the row tr, gated on
     `pointerType === "touch"` && `effectiveDragEnabled`. Capture
     phase chosen so handlers don't collide with safeRowDragListeners
     (which spreads dnd-kit's bubble-phase onPointerDown).
   - Visual cue: subtle `inset 0 0 0 2px hexToRgba(highlightColor,
     0.45)` ring composed AFTER quickOpen/selection/current rings so
     it never outranks a real selection state. Two defensive
     useEffects clear `isPressing`: one when drag becomes disabled
     (capability flip / edit-mode toggle), one tied to the
     `isDragging` lifecycle so dnd-kit-managed release paths (drop
     outside the row, sensor-managed cancel) can't leave a stale
     ring behind.
   - data-pressing="true" attribute exposed on tr for tests.
   - TouchSensor delay (200ms) and tolerance (8px) unchanged.

Tests added:
 - executive-meetings-quick-actions-dialog-layout.spec.mjs: asserts
   close button + title bounding boxes don't overlap in AR + EN.
 - executive-meetings-row-touch-press-feedback.spec.mjs: uses
   hasTouch+isMobile + CDP Input.dispatchTouchEvent, asserts
   data-pressing flips true on touchstart, clears on touchend, and
   tap-to-open quick-actions dialog still works for short taps;
   second test verifies the cue persists past the 200ms window.

Verification: pnpm tsc --noEmit clean. Pre-existing `test` workflow
failures (rotate-content/font-settings/notes-share) are unrelated to
this change.
2026-05-11 15:40:48 +00:00
riyadhafraa 27c5cc381c Task #499: fix Quick Actions dialog X overlap + iPad long-press drag feedback
Two scoped UX fixes on Executive Meetings schedule, both confined to
artifacts/tx-os/src/pages/executive-meetings.tsx (shared
src/components/ui/dialog.tsx untouched per task spec).

1) Quick Actions dialog header overlap
   - Radix DialogContent renders the Close (X) at physical
     `right-4 top-4` regardless of `dir`. In AR (RTL) the
     `text-right`-aligned title "إجراءات سريعة" pinned to the same
     edge and visibly overlapped the X.
   - Fix: added `pr-8` to the per-row Quick Actions DialogHeader so
     the title's right edge always clears the close button. Works
     for AR (overlap eliminated) and EN (just adds trailing slack).

2) iPad long-press drag feedback
   - dnd-kit's TouchSensor has a 200ms activation delay; previously
     the row gave no cue during the hold window so users lifted
     early thinking the schedule didn't register.
   - Fix: added `isPressing` state toggled via capture-phase
     onPointerDown/Up/Cancel handlers on the row tr, gated on
     `pointerType === "touch"` && `effectiveDragEnabled`. Capture
     phase chosen so handlers don't collide with safeRowDragListeners
     (which spreads dnd-kit's bubble-phase onPointerDown).
   - Visual cue: subtle `inset 0 0 0 2px hexToRgba(highlightColor,
     0.45)` ring composed AFTER quickOpen/selection/current rings so
     it never outranks a real selection state. Defensive useEffect
     clears `isPressing` if drag becomes disabled mid-press.
   - data-pressing="true" attribute exposed on tr for tests.
   - TouchSensor delay (200ms) and tolerance (8px) unchanged.

Tests added:
 - executive-meetings-quick-actions-dialog-layout.spec.mjs: asserts
   close button + title bounding boxes don't overlap in AR + EN.
 - executive-meetings-row-touch-press-feedback.spec.mjs: uses
   hasTouch+isMobile + CDP Input.dispatchTouchEvent, asserts
   data-pressing flips true on touchstart, clears on touchend, and
   tap-to-open quick-actions dialog still works for short taps;
   second test verifies the cue persists past the 200ms window.

Verification: pnpm tsc --noEmit clean. Pre-existing `test` workflow
failures (rotate-content/font-settings/notes-share) are unrelated to
this change.
2026-05-11 15:38:17 +00:00
riyadhafraa 08ebd906a8 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: e58022a2-ac79-4eb8-b08f-2664061060ad
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 15:31:39 +00:00
riyadhafraa b5b55f89fb Task #497: Allow row-drag rotation without time windows
Lifted the per-row missing-time drag block (originally #492). Meetings
without a (startTime, endTime) tuple are a normal state and now rotate
freely alongside timed rows.

Server (artifacts/api-server/src/routes/executive-meetings.ts):
- Dropped the `no_time_window` early-return guard in
  /api/executive-meetings/rotate-content.
- Sort + slot construction now tolerate null start times (NULLS LAST,
  tie-break by id) so a null tuple rotates as a real slot.
- All other safeguards (cancelled_in_rotate, optimistic lock,
  renumberDayByStartTime, audit logging) remain intact.

Client (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Removed missingTimeCount / dayRotatable memos, the dayRotatable
  prop wiring, dragBlockedByMissingTime + effectiveDragEnabled
  composition, the row's data-drag-blocked / native title /
  cursor-not-allowed-opacity-80 / aria-disabled overrides, the entire
  DragBlockedTooltipButton component + its render site, and the
  no_time_window errorToast branch in rotateContent. Pruned now-dead
  Tooltip + AlertTriangle imports.

i18n: removed the `executiveMeetings.rotate.needsTimeWindow` block
(tooltip + errorToast) from en.json and ar.json.

Tests: deleted `executive-meetings-rotate-needs-time-window.spec.mjs`
and added `executive-meetings-rotate-allows-missing-times.spec.mjs`,
which inserts 3 meetings (one with null times), verifies no
drag-blocked warning button / aria-disabled, drags the top row to the
bottom slot, asserts the rotate-content POST succeeds, and confirms
exactly one row still has a null tuple after rotation.

Verified: row-drag, row-quick-actions, and the new spec all pass
(8/8). Architect code review PASSED.
2026-05-11 15:23:33 +00:00
riyadhafraa 59d838b8f2 Task #497: Allow row-drag rotation without time windows
Lifted the per-row missing-time drag block (originally #492). Meetings
without a (startTime, endTime) tuple are a normal state and now rotate
freely alongside timed rows.

Server (artifacts/api-server/src/routes/executive-meetings.ts):
- Dropped the `no_time_window` early-return guard in
  /api/executive-meetings/rotate-content.
- Sort + slot construction now tolerate null start times (NULLS LAST,
  tie-break by id) so a null tuple rotates as a real slot.
- All other safeguards (cancelled_in_rotate, optimistic lock,
  renumberDayByStartTime, audit logging) remain intact.

Client (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Removed missingTimeCount / dayRotatable memos, the dayRotatable
  prop wiring, dragBlockedByMissingTime + effectiveDragEnabled
  composition, the row's data-drag-blocked / native title /
  cursor-not-allowed-opacity-80 / aria-disabled overrides, the entire
  DragBlockedTooltipButton component + its render site, and the
  no_time_window errorToast branch in rotateContent. Pruned now-dead
  Tooltip + AlertTriangle imports.

i18n: removed the `executiveMeetings.rotate.needsTimeWindow` block
(tooltip + errorToast) from en.json and ar.json.

Tests: deleted `executive-meetings-rotate-needs-time-window.spec.mjs`
and added `executive-meetings-rotate-allows-missing-times.spec.mjs`,
which inserts 3 meetings (one with null times), verifies no
drag-blocked warning button / aria-disabled, drags the top row to the
bottom slot, asserts the rotate-content POST succeeds, and confirms
exactly one row still has a null tuple after rotation.

Verified: row-drag, row-quick-actions, and the new spec all pass
(8/8). Architect code review PASSED.
2026-05-11 15:18:06 +00:00
riyadhafraa e59b4919ec 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: e2882e59-6f8d-437a-a002-6d635e649b7d
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 15:02:35 +00:00
riyadhafraa ecc6f3efc4 Prevent quick actions popover from opening when in edit mode
Update conditional logic for opening the quick actions popover to account for edit mode, ensuring it only opens when mutation is allowed and edit mode is disabled.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 70a0c645-f927-4c80-a04a-32ae50f404f9
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 14:58:22 +00:00
riyadhafraa 34dd8d50a4 Task #496: hide row Quick Actions in edit mode + drop missing-time banner
- ScheduleSection: gate `quickActionsCanMutate` on `canMutate && !editMode`
  so clicking a row in edit mode no longer opens the Postpone dialog.
  In view mode the quick-actions surface still works as before.
- MeetingRow: add a useEffect that force-closes local `quickOpen` when
  capability flips off (edit-mode toggle while dialog is open) and gate
  `quickOpenShadow` + `data-quick-open` on capability so no stale frame
  paints. Caught by code review.
- Remove the amber `em-rotate-blocked-hint` missing-time banner per
  product feedback. Kept `missingTimeCount` / `dayRotatable` so row
  drag still aborts client-side and the per-row tooltip + native
  title still explain why.
- Drop now-unused i18n keys `executiveMeetings.rotate.needsTimeWindow.hint*`
  from en.json and ar.json (zero/one/two/few/many/other variants).
  `tooltip` + `errorToast` strings stay (consumed by tooltip button
  + rotate-content catch handler).
- Update executive-meetings-rotate-needs-time-window.spec.mjs to drop
  the banner visibility assertion and the post-unblock `toHaveCount(0)`
  re-check. Tooltip / aria-disabled / no-rotate-POST / successful-
  rotate-after-unblock checks remain.

Verified: rotate-needs-time-window + row-quick-actions specs (7 tests)
all pass with --workers=1.
2026-05-11 14:57:13 +00:00
riyadhafraa 8fd16eb2dc Block EM row-drag when any meeting on the day lacks a time window (#492)
The /api/executive-meetings/rotate-content endpoint hard-rejects with
`code: "no_time_window"` whenever any visible non-cancelled meeting on
the date is missing (start_time, end_time). Before this change, dragging
a row on such a day produced an opaque English error toast that
confused AR users and didn't tell them what to fix.

UI changes:
- Compute `missingTimeCount` / `dayRotatable` from `orderedMeetings`
  in executive-meetings.tsx and thread `dayRotatable` into MeetingRow.
- MeetingRow gates useSortable + safeRowDragListeners on
  `effectiveDragEnabled = dragEnabled && dayRotatable`. When drag is
  blocked specifically by missing time, the <tr> shows
  `cursor-not-allowed`, dimmed opacity, `aria-disabled="true"`,
  `data-drag-blocked="no_time_window"`, and a bilingual native `title`
  (desktop hover fallback). The aria-disabled is spread AFTER dnd-kit's
  attributes so it wins the prop merge.
- New `DragBlockedTooltipButton` rendered inside each blocked row's #
  cell — a Radix Tooltip-wrapped warning icon that opens on hover,
  focus, AND tap (manual open toggle) so iPad users can read the
  explanation (native title long-press is unreliable on touch).
- Inline amber `em-rotate-blocked-hint` banner above the bulk toolbar
  surfaces the missing-time count using i18next's CLDR plural resolver
  (`t(key, { count })`) so AR picks the correct
  zero/one/two/few/many/other form and EN picks one/other.
- rotateContent's catch handler detects ApiError code "no_time_window"
  (local apiJson now attaches .code/.status to thrown Errors) and
  shows the bilingual `executiveMeetings.rotate.needsTimeWindow.errorToast`
  instead of the raw server message.
- Locale keys added under `executiveMeetings.rotate.needsTimeWindow`
  in en.json + ar.json (tooltip, hint plurals, errorToast). AR has
  full zero/one/two/few/many/other variants.
- ScheduleSection's `t` prop type widened to accept i18next options.

Tests:
- New tests/executive-meetings-rotate-needs-time-window.spec.mjs:
  inserts one untimed + two timed meetings, asserts the hint banner +
  every row's aria-disabled + data-drag-blocked, asserts the
  Tooltip-wrapped info button is visible with the correct localized
  aria-label + tooltip body (substring match — Radix renders sr-only
  duplicate), attempts a drag and asserts NO rotate-content POST is
  sent and DB state is unchanged. Then UPDATE-s the missing time and
  asserts: hint disappears, aria-disabled lifts on every row, info
  button is gone, drag now succeeds (rotate-content POST + DB
  start_time mutation).
- Existing executive-meetings-row-drag, touch-reorder, and
  row-quick-actions specs still pass.

Files: artifacts/tx-os/src/pages/executive-meetings.tsx,
       artifacts/tx-os/src/locales/{en,ar}.json,
       artifacts/tx-os/tests/executive-meetings-rotate-needs-time-window.spec.mjs
Server route untouched.
2026-05-11 14:35:19 +00:00
riyadhafraa e19506aac7 Block EM row-drag when any meeting on the day lacks a time window (#492)
The /api/executive-meetings/rotate-content endpoint hard-rejects with
`code: "no_time_window"` whenever any visible non-cancelled meeting on
the date is missing (start_time, end_time). Before this change, dragging
a row on such a day produced an opaque English error toast ("Every
meeting must have a scheduled time window to rotate") that confused AR
users and didn't tell them what to fix.

Fix:
- Compute `missingTimeCount` / `dayRotatable` from `orderedMeetings`
  in executive-meetings.tsx and thread `dayRotatable` into MeetingRow.
- MeetingRow now gates useSortable + safeRowDragListeners on
  `effectiveDragEnabled = dragEnabled && dayRotatable`. When drag is
  blocked specifically by missing time, the <tr> shows
  `cursor-not-allowed`, dimmed opacity, `aria-disabled="true"`,
  `data-drag-blocked="no_time_window"`, and a bilingual `title`
  tooltip. The aria-disabled is spread AFTER dnd-kit's attributes so
  it wins the prop merge.
- Inline amber `em-rotate-blocked-hint` banner above the bulk toolbar
  surfaces the missing-time count so mutators see why drag is paused.
- rotateContent's catch handler now detects ApiError code
  "no_time_window" (local apiJson now attaches .code/.status to the
  thrown Error) and shows the bilingual
  `executiveMeetings.rotate.needsTimeWindow.errorToast` instead of
  the raw server message.
- Locale keys added under `executiveMeetings.rotate.needsTimeWindow`
  in en.json + ar.json (tooltip, hint_one/_other [+ AR plurals],
  errorToast).

Tests:
- New tests/executive-meetings-rotate-needs-time-window.spec.mjs
  inserts one untimed + two timed meetings and asserts the hint
  banner + every row's aria-disabled + data-drag-blocked, then
  attempts a drag and asserts NO rotate-content POST is sent and
  DB state is unchanged. After UPDATE-ing the missing time, asserts
  the hint and aria-disabled lift.
- Existing executive-meetings-row-drag and touch-reorder specs still
  pass — drag works exactly as before on fully-timed days.

Files: artifacts/tx-os/src/pages/executive-meetings.tsx,
       artifacts/tx-os/src/locales/{en,ar}.json,
       artifacts/tx-os/tests/executive-meetings-rotate-needs-time-window.spec.mjs
Server route untouched.
2026-05-11 14:21:02 +00:00
riyadhafraa 71e9b6abda 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: 6bdf8651-857f-45f3-8acd-5c1e9dbda4be
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 14:06:04 +00:00
riyadhafraa 38a55b8b42 Task #491: center quick-actions surface + frame the selected row
The user reported that the small quick-actions popover (which holds
the Postpone trigger) was hard to find — it floated next to the
clicked row and they sometimes lost track of which meeting they
were about to act on. They asked for the surface to appear in the
center of the screen, and for the row they clicked to be clearly
framed in a color while the surface is open.

Changes:
- Replaced the row's row-anchored Popover (PopoverAnchor +
  PopoverContent) with a centered Dialog (DialogContent) that
  Radix renders into a portal. The Dialog uses the existing
  `executiveMeetings.quickActions.label` translation key for its
  title (already present in both Arabic and English) and reuses
  #490's polished Postpone Button unchanged. Test ID stays
  `em-row-quick-${id}` so existing e2e specs continue to target
  the same surface.
- Added a third inset-shadow ring (`inset 0 0 0 3px
  ${highlightColor}`) to the row's composed boxShadow stack while
  the row's quickOpen state is true. Listed first in the stack so
  it becomes the outermost frame and wins visually over the
  existing current-meeting / bulk-select rings. Uses the
  page-level highlightPrefs.color so the frame matches the user's
  chosen accent. Also exposes `data-quick-open="true"` on the
  <tr> for tests.
- Updated the row-quick-actions e2e: the postpone-end-to-end test
  now asserts `role="dialog"` on the surface and the row carries
  `data-quick-open` while open and loses it after Postpone is
  clicked. Added a separate test for Escape closing the surface
  and clearing the row frame.

Verified: 5/5 quick-actions tests pass, plus both drag specs
(row-drag + iPad touch-reorder) still pass.
2026-05-11 13:28:55 +00:00
riyadhafraa c7942a221f Task #491: center quick-actions surface + frame the selected row
The user reported that the small quick-actions popover (which holds
the Postpone trigger) was hard to find — it floated next to the
clicked row and they sometimes lost track of which meeting they
were about to act on. They asked for the surface to appear in the
center of the screen, and for the row they clicked to be clearly
framed in a color while the surface is open.

Changes:
- Replaced the row's row-anchored Popover (PopoverAnchor +
  PopoverContent) with a centered Dialog (DialogContent) that
  Radix renders into a portal. The Dialog uses the existing
  `executiveMeetings.quickActions.label` translation key for its
  title (already present in both Arabic and English) and reuses
  #490's polished Postpone Button unchanged. Test ID stays
  `em-row-quick-${id}` so existing e2e specs continue to target
  the same surface.
- Added a third inset-shadow ring (`inset 0 0 0 3px
  ${highlightColor}`) to the row's composed boxShadow stack while
  the row's quickOpen state is true. Listed first in the stack so
  it becomes the outermost frame and wins visually over the
  existing current-meeting / bulk-select rings. Uses the
  page-level highlightPrefs.color so the frame matches the user's
  chosen accent. Also exposes `data-quick-open="true"` on the
  <tr> for tests.
- Updated the row-quick-actions e2e: the postpone-end-to-end test
  now asserts `role="dialog"` on the surface and the row carries
  `data-quick-open` while open and loses it after Postpone is
  clicked. Added a separate test for Escape closing the surface
  and clearing the row frame.

Verified: 5/5 quick-actions tests pass, plus both drag specs
(row-drag + iPad touch-reorder) still pass.
2026-05-11 13:25:21 +00:00
riyadhafraa f1c9ef71b7 Task #491: center quick-actions surface + frame the selected row
The user reported that the small quick-actions popover (which holds
the Postpone trigger) was hard to find — it floated next to the
clicked row and they sometimes lost track of which meeting they
were about to act on. They asked for the surface to appear in the
center of the screen, and for the row they clicked to be clearly
framed in a color while the surface is open.

Changes:
- Replaced the row's row-anchored Popover (PopoverAnchor +
  PopoverContent) with a centered Dialog (DialogContent) that
  Radix renders into a portal. The Dialog uses the existing
  `executiveMeetings.quickActions.label` translation key for its
  title (already present in both Arabic and English) and reuses
  #490's polished Postpone Button unchanged. Test ID stays
  `em-row-quick-${id}` so existing e2e specs continue to target
  the same surface.
- Added a third inset-shadow ring (`inset 0 0 0 3px
  ${highlightColor}`) to the row's composed boxShadow stack while
  the row's quickOpen state is true. Listed first in the stack so
  it becomes the outermost frame and wins visually over the
  existing current-meeting / bulk-select rings. Uses the
  page-level highlightPrefs.color so the frame matches the user's
  chosen accent. Also exposes `data-quick-open="true"` on the
  <tr> for tests.
- Updated the row-quick-actions e2e: the postpone-end-to-end test
  now asserts `role="dialog"` on the surface and the row carries
  `data-quick-open` while open and loses it after Postpone is
  clicked. Added a separate test for Escape closing the surface
  and clearing the row frame.

Verified: 5/5 quick-actions tests pass, plus both drag specs
(row-drag + iPad touch-reorder) still pass.
2026-05-11 13:22:58 +00:00
riyadhafraa c5ae9e8832 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: 960cf934-7f26-4bca-88b5-dfe83f1db67a
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 13:16:43 +00:00
riyadhafraa bcda13d9c8 Ensure postpone button styling and text are correct for all devices
Add assertions to verify the styling classes and text content of the postpone button in the executive meetings row quick actions spec.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: bf387ee6-aedd-4092-acfb-d6a42a1969f2
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 13:03:39 +00:00
riyadhafraa c7168a959b Task #490: iPad row drag + Postpone button polish
The user reported that on iPad they could no longer drag a meeting
row up or down, and that the Postpone button in the row quick-actions
popover looked plain.

Changes:
- Sensor split for the schedule row DnD: replaced PointerSensor with
  MouseSensor (mouse only, distance:6) + kept TouchSensor (touch,
  delay:200ms / tolerance:8px). On iOS Safari, PointerSensor was
  receiving touch-derived pointer events and competing with the OS
  scroll claim — the browser reclaimed the gesture as a vertical
  scroll before the finger moved 6px, so drag never activated.
  Splitting sensors lets each input modality use the activation
  constraint that actually works for it. PointerSensor remains
  imported because the attendee-reorder dialog still uses it
  (out of scope).
- Polished the Postpone button in the row quick-actions popover:
  swapped the bespoke <button> for the project's <Button> component
  (variant=default, size=sm), gave it a 40px tap target (h-10),
  shadow-sm, gap-2, min-w-[11rem], and a flex-row-reverse flip in
  RTL so the icon stays adjacent to the label in Arabic.
- Rewrote tests/executive-meetings-touch-reorder.spec.mjs which was
  stale — it referenced the removed grip and the retired /reorder
  endpoint. New spec runs under iPad emulation (hasTouch + isMobile,
  768x1024), clears the persisted edit-mode pref + asserts edit mode
  reads OFF (so the "drag works without edit mode" regression is
  explicitly guarded), long-presses the # cell, drags down, and
  asserts /api/executive-meetings/rotate-content fires + DB rotation
  matches expectation.

Tests: touch-reorder (iPad TouchSensor), row-drag (desktop mouse via
MouseSensor), and popover (4/4) all pass. Backend unchanged.
2026-05-11 12:58:17 +00:00
riyadhafraa 40cfe56c3b 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: 8ca1a162-0ab1-4c43-a9ee-fcba1f4b26e1
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 12:50:47 +00:00
riyadhafraa 5a0c8a9ed1 Task #489 fix: decouple row drag from edit mode + fix optimistic order
Previous review rejected because row drag was gated on
`effectiveCanMutate = canMutate && editMode`, forcing users to
enter Edit mode to drag. Spec requires drag to work in view mode
for any user with mutate permission.

Second rejection: optimistic UI patched per-meeting (start/end/
dailyNumber) but did not reorder the rendered rows, so post-drop
slots could display rotated times against the wrong content
(e.g. 11:00, 09:00, 10:00) until refetch.

Changes:
- MeetingRow: new `dragEnabled` prop separate from `canMutate`.
  Drives useSortable.disabled, safeRowDragListeners gate, attributes
  spread, and cursor-grab class.
- Parent passes `dragEnabled={canMutate}` (raw, not gated by
  editMode); `canMutate={effectiveCanMutate}` retained for inline
  cell editing.
- handleRowClick: scope interactive `closest()` to descendants
  (`interactive !== e.currentTarget`). Required because the row now
  carries dnd-kit's role="button" in view mode and an unscoped
  closest() matched the row itself, swallowing every popover click.
- rotateContent: also call `setOptimisticOrder(newOrder)` so slot[i]
  visually shows newOrder[i] in the same React commit; rollback
  restores the prior optimistic order on rotate failure.
- e2e: removed edit-toggle click from row-drag spec — drag now
  passes in view mode.

Tests: row-drag spec (view mode), popover spec (4/4), schedule-
features drag tests (290, 834), backend rotate-content (6/6) all
pass. 3 unrelated schedule-features failures reference a missing
em-nav-settings testid that does not exist in source — pre-existing
broken tests, not caused by this task.
2026-05-11 12:36:04 +00:00
riyadhafraa f4f94981f8 Task #489 fix: decouple row drag from edit mode
Previous review rejected because row drag was gated on
`effectiveCanMutate = canMutate && editMode`, forcing users to
enter Edit mode to drag. Spec requires drag to work in view mode
for any user with mutate permission.

Changes:
- MeetingRow: new `dragEnabled` prop separate from `canMutate`.
  Used by useSortable.disabled, safeRowDragListeners gate,
  attributes spread, and cursor-grab class.
- Parent passes `dragEnabled={canMutate}` (raw, not gated by
  editMode); `canMutate={effectiveCanMutate}` retained for inline
  cell editing.
- handleRowClick: scope interactive `closest()` to descendants
  (interactive !== e.currentTarget). Required because the row now
  carries dnd-kit's role="button" in view mode and an unscoped
  closest() matched the row itself, swallowing every popover click.
- e2e: removed edit-toggle click from row-drag spec — drag now
  passes in view mode.

Tests: row-drag spec (view mode), popover spec (4/4), schedule-
features drag tests (290, 834), backend rotate-content (6/6) all
pass. 3 unrelated schedule-features failures reference a missing
em-nav-settings testid that does not exist in source — pre-existing
broken tests, not caused by this task.
2026-05-11 12:28:46 +00:00
riyadhafraa f53f7307da Task #489: row-wide drag rotates meeting content; time + daily numbers anchored
Backend
- New POST /api/executive-meetings/rotate-content (zod-validated) rotates
  ONLY meeting content through fixed (start_time, end_time, daily_number)
  slots. Same-date enforced; per-meeting expectedUpdatedAt → 409 stale;
  incomplete day (missing visible row) → 400.
- ExecutiveMeetingsRotateContentBody added in lib/api-zod (manual.ts).
- 6 backend tests cover happy path, stale, different_dates, 401, 403,
  incomplete_day. Existing /swap-times tests still pass.

Frontend (artifacts/tx-os)
- Whole <tr> is now the drag handle (the dedicated GripVertical button is
  retired). useSortable is gated on canMutate; safeRowDragListeners
  filters drags whose target is an interactive descendant (button, input,
  edit/time cells, row-actions, bulk-select). useSortable `attributes`
  are spread only when canMutate so view-mode rows stay clickable
  (otherwise aria-disabled blocked the popover trigger).
- onRowDragEnd → rotateContent(fromId, toId): optimistic patch reassigns
  each chronological slot's tuple to the new occupant; rolls back + toast
  on failure.
- Quick-actions popover now contains only Postpone (#486 Move up/down
  buttons removed).

Tests (artifacts/tx-os)
- New tests/executive-meetings-row-drag.spec.mjs: drags Alpha → Charlie
  position by # cell, asserts rotate-content fires and slots stay
  anchored.
- tests/executive-meetings-row-quick-actions.spec.mjs: drops up/down
  cases, keeps Postpone + skip-surfaces + viewer.
- tests/executive-meetings-schedule-features.spec.mjs: two legacy grip
  drag tests rewritten to drag the row body and target /rotate-content
  (the legacy /reorder route + tests are intentionally untouched).

Drift / notes
- Architect flagged a medium-severity hardening note: rotate-content
  FOR UPDATE locks orderedIds but not the day-scope completeness query.
  Out of #489 scope; no follow-up created (proposeFollowUpTasks was
  already consumed on #486).
2026-05-11 12:14:08 +00:00
riyadhafraa 82c7894e56 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: 0c08b89c-4888-4884-a26e-a0aed06fb429
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 11:33:18 +00:00
riyadhafraa 936e184b05 #486: Executive Meetings row click → quick-actions popover
Clicking any meeting row on the Executive Meetings schedule (gated only
on canMutate, not editMode) opens a small popover with Move up / Move
down / Postpone. Move up/down swap only the (startTime, endTime) tuple
between the clicked meeting and its chronological neighbour on the same
date — the Time column stays visually anchored to its row position.

Backend
- POST /executive-meetings/swap-times: transactional swap with FOR
  UPDATE row locking, optimistic-lock conflict shape (stale_meeting +
  conflict payload), date/time-window guards, audit logging, and
  renumberDayByStartTime + day-changed broadcast.
- Zod schema in lib/api-zod/src/manual.ts.

Frontend
- Shared lib/api-json.ts JSON helper.
- ScheduleSection.swapTimes does an optimistic (startTime, endTime)
  swap against the day query cache and rolls back on failure (mirrors
  the existing inline-edit UX).
- Edge enablement (canQuickMoveUp/Down) now derived from the full
  orderedMeetings index, not the search-filtered displayedMeetings, so
  "Move up/down" disabled state always matches the true day boundary
  (and never silently swaps with a hidden filtered neighbour).
- MeetingRow uses Popover/PopoverAnchor with skip rules: ARIA roles
  (button/checkbox/switch/combobox/dialog) and em-time-* / em-edit-* /
  em-row-grip / em-row-actions data-testid prefixes do NOT open the
  popover.
- PostponeDialog reused from upcoming-meeting-alert.tsx.

Tests
- Backend swap-times: happy path, stale_meeting (409), different_dates
  (400), no_time_window (400), unauth (401), viewer-no-mutate (403),
  malformed-timestamp (400) — all 7 pass.
- Hardened expectedUpdatedAt zod schema to z.string().datetime() so
  malformed tokens fail at validation with a controlled 400 instead of
  bubbling up as a 500.
- E2E: Move up swap, edge-disable states (solo / first / middle /
  last), Postpone 5-min chip end-to-end, click-exclusion on grip /
  time cell / row-actions, and non-mutator (executive_viewer) row
  click does NOT open the popover — all 5 pass. Each test uses its own
  future date to avoid cross-test pollution.

Code review approved on second pass. Pre-existing failures in other
suites (executive-meetings reorder, font-settings, notes-share,
service-orders) are unrelated to this task and predate it.

Follow-ups proposed: #487 (keyboard a11y on the popover), #488 (edit-
mode test gaps).
2026-05-11 11:13:19 +00:00
riyadhafraa d4cc77c8cc #486: Executive Meetings row click → quick-actions popover
Clicking any meeting row on the Executive Meetings schedule (gated only
on canMutate, not editMode) opens a small popover with Move up / Move
down / Postpone. Move up/down swap only the (startTime, endTime) tuple
between the clicked meeting and its chronological neighbour on the same
date — the Time column stays visually anchored to its row position.

Backend
- POST /executive-meetings/swap-times: transactional swap with FOR
  UPDATE row locking, optimistic-lock conflict shape (stale_meeting +
  conflict payload), date/time-window guards, audit logging, and
  renumberDayByStartTime + day-changed broadcast.
- Zod schema in lib/api-zod/src/manual.ts.

Frontend
- Shared lib/api-json.ts JSON helper.
- ScheduleSection.swapTimes does an optimistic (startTime, endTime)
  swap against the day query cache and rolls back on failure (mirrors
  the existing inline-edit UX).
- Edge enablement (canQuickMoveUp/Down) now derived from the full
  orderedMeetings index, not the search-filtered displayedMeetings, so
  "Move up/down" disabled state always matches the true day boundary
  (and never silently swaps with a hidden filtered neighbour).
- MeetingRow uses Popover/PopoverAnchor with skip rules: ARIA roles
  (button/checkbox/switch/combobox/dialog) and em-time-* / em-edit-* /
  em-row-grip / em-row-actions data-testid prefixes do NOT open the
  popover.
- PostponeDialog reused from upcoming-meeting-alert.tsx.

Tests
- Backend swap-times: happy path, stale_meeting (409), different_dates
  (400), no_time_window (400), unauth (401), viewer-no-mutate (403),
  malformed-timestamp (400) — all 7 pass.
- Hardened expectedUpdatedAt zod schema to z.string().datetime() so
  malformed tokens fail at validation with a controlled 400 instead of
  bubbling up as a 500.
- E2E: Move up swap, edge-disable states (solo / first / middle /
  last), Postpone 5-min chip end-to-end, click-exclusion on grip /
  time cell / row-actions — all 4 pass. Each test uses its own future
  date to avoid cross-test pollution.

Code review approved on second pass. Pre-existing failures in other
suites (executive-meetings reorder, font-settings, notes-share,
service-orders) are unrelated to this task and predate it.

Follow-ups proposed: #487 (keyboard a11y on the popover), #488 (edit-
mode test gaps).
2026-05-11 11:09:16 +00:00