Commit Graph

367 Commits

Author SHA1 Message Date
Replit Agent e2564e2a66 Add download for protocol visit photos (Task #660)
Users could upload photos into Protocol "تصوير" albums but had no way to
download them — clicking a photo only opened it inline. This adds per-photo
downloads and a whole-album ZIP download.

Backend:
- objectStorage.ts: new exported extensionForContentType() mapping content
  types to file extensions (stored paths are extension-less UUIDs).
- storage.ts: opt-in ?download=1 mode on GET /storage/objects/*path sets
  Content-Disposition: attachment (with optional ?name base + derived ext);
  default inline behavior and per-entity authz unchanged.
- protocol.ts: new GET /protocol/photo-albums/:id/download streams all album
  photos as a ZIP (guarded by requireProtocolAccess), named from the album;
  unreadable objects are skipped rather than failing the archive. Empty album
  returns 404.
- Added archiver@8 dependency (+@types/archiver dev).

Frontend (protocol.tsx):
- Per-photo download button (attachment, name = album-index) in album detail.
- "تحميل الكل" / "Download all" ZIP button in album header; rendered disabled
  (not hidden) when the album has no photos.
- Added photoDownloadSrc/albumDownloadSrc helpers, Download icon.
- AR/EN locale strings protocol.photos.download / downloadAll.

Notes / deviations:
- archiver v8 is ESM-only with no callable default export; used
  `new ZipArchive(...)` instead of `archiver("zip")`. Recorded in memory.
- Could not run authenticated e2e (seed admin password is a secret). Verified:
  api-server typecheck + build pass, tx-os typecheck passes, server healthy,
  new routes wired (401 auth-required not 404). Pre-existing typecheck errors
  in executive-meetings.ts / push.ts are unrelated and untouched.
2026-07-07 09:37:15 +00:00
Replit Agent 56c8bcfb85 Add download for protocol visit photos (Task #660)
Users could upload photos into Protocol "تصوير" albums but had no way to
download them — clicking a photo only opened it inline. This adds per-photo
downloads and a whole-album ZIP download.

Backend:
- objectStorage.ts: new exported extensionForContentType() mapping content
  types to file extensions (stored paths are extension-less UUIDs).
- storage.ts: opt-in ?download=1 mode on GET /storage/objects/*path sets
  Content-Disposition: attachment (with optional ?name base + derived ext);
  default inline behavior and per-entity authz unchanged.
- protocol.ts: new GET /protocol/photo-albums/:id/download streams all album
  photos as a ZIP (guarded by requireProtocolAccess), named from the album;
  unreadable objects are skipped rather than failing the archive. Empty album
  returns 404.
- Added archiver@8 dependency (+@types/archiver dev).

Frontend (protocol.tsx):
- Per-photo download button (attachment, name = album-index) in album detail.
- "تحميل الكل" / "Download all" ZIP button in album header (hidden when empty).
- Added photoDownloadSrc/albumDownloadSrc helpers, Download icon.
- AR/EN locale strings protocol.photos.download / downloadAll.

Notes / deviations:
- archiver v8 is ESM-only with no callable default export; used
  `new ZipArchive(...)` instead of `archiver("zip")`. Recorded in memory.
- Could not run authenticated e2e (seed admin password is a secret). Verified:
  api-server typecheck + build pass, tx-os typecheck passes, server healthy,
  new routes wired (401 auth-required not 404). Pre-existing typecheck errors
  in executive-meetings.ts / push.ts are unrelated and untouched.
2026-07-07 09:34:06 +00:00
Replit Agent 3f3c9671f5 Add functionality to manage visit photos and albums for protocols
Introduce new database tables for photo albums and photos, add API endpoints for CRUD operations on albums and photos, and implement frontend components for managing photo albums.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ad3facca-9626-4e19-95c0-125e6d3bc7e2
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1OXOhLE
Replit-Helium-Checkpoint-Created: true
2026-07-07 09:14:07 +00:00
Replit Agent aaa2d0b48d Make external meeting titles display in red
Update the styling of the MeetingRow component to conditionally apply red text color to the meeting title when the `isExternal` property is true.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1f41191d-7d28-4e72-bd38-191f56bae01b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/bJodRQl
Replit-Helium-Checkpoint-Created: true
2026-07-07 08:24:00 +00:00
Replit Agent 17950daa5e feat(protocol): shareable public booking link bar in Room Bookings
Task #655: staff need a one-click way to copy and share the public
(no-login) room-booking link with guests.

- Add a link bar at the top of the "حجوزات القاعات" (Room Bookings)
  section showing the absolute public URL, with a "نسخ الرابط" copy
  button that gives toast + a temporary "تم النسخ" confirmation state.
- URL is built from window.location.origin + BASE_URL (trailing slash
  stripped) + "/protocol/request", so it works on root and subpath
  deploys and when pasted into a browser/WhatsApp.
- Clipboard uses navigator.clipboard with a textarea+execCommand
  fallback; fallback now checks execCommand's return and reports failure
  (destructive toast) instead of falsely showing success. Textarea is
  removed in a finally block. Copied-state timer stored in a ref and
  cleared on repeat clicks to avoid a reset race.
- URL text forced dir="ltr" inside the RTL layout; new i18n keys
  (shareLinkTitle, copyLink, linkCopied, linkCopyFailed) added under
  protocol.bookings in both ar.json and en.json.

Verified: tsc typecheck passes; /protocol serves 200; module transforms
cleanly via Vite; architect review issues (fallback false-success,
timer race) fixed.
2026-07-06 12:20:17 +00:00
Replit Agent 457eff44c4 Add professional Arabic date and time picker to booking form
Introduce a custom DateTimePicker component for improved date and time selection in the protocol request form, replacing native inputs with an Arabic-localized, RTL-compatible interface.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 8b01db58-6cde-464b-bd7d-46bd031e467f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/OsTuUKE
Replit-Helium-Checkpoint-Created: true
2026-07-06 11:40:36 +00:00
Replit Agent 2a194de12c Add attendee count field to booking requests and display it
Updates API schemas and database to include attendee count for bookings, modifies the public request form to accept this new optional field, and displays the attendee count on the booking details.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 9c655ed7-866c-454d-b646-e458df854c65
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/OsTuUKE
Replit-Helium-Checkpoint-Created: true
2026-07-06 11:33:58 +00:00
Replit Agent 62c38a508f Add public interface for submitting room booking requests
Introduces a new public-facing API endpoint and UI for submitting room booking requests without authentication, including rate limiting and input validation.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 5352d582-4c15-426c-84ce-4ba3fa0d5cd8
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/OsTuUKE
Replit-Helium-Checkpoint-Created: true
2026-07-06 11:18:14 +00:00
Replit Agent 5d76d25eb7 Move navigation to a vertical sidebar on the right for better layout
Restructures the page layout to move the navigation tabs from a horizontal bar to a vertical sidebar on the right, enhancing RTL support.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ec325dd2-ed6f-4785-8de9-93ec3207af08
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/9SfnFMU
Replit-Helium-Checkpoint-Created: true
2026-07-06 10:13:41 +00:00
Replit Agent 5e460920c7 Task #649: complete Public Relations & Protocol module (fix 8 review gaps)
Addresses the 8 spec gaps that blocked prior completion, all additive and
scoped to the protocol_* module / /protocol routes. executive-meetings untouched.

Backend (api-server/routes/protocol.ts, lib/db schema, seed):
- External meetings create as "pending" (status dropped from create schema);
  added approve + reject endpoints with state-conditional WHERE status='pending'
  and 409 on contention; patch status enum restricted to completed/cancelled.
- Added approvedBy/approvedAt/rejectionReason columns + status index; default
  status now "pending".
- Booking reject and gift-issue reject made state-conditional (409 on 0 rows).
- Dashboard: roomsAvailableNow (NOT EXISTS), giftsIssuedThisMonth/Qty;
  upcomingExternal filtered to future pending|approved.
- Reports: added externalMeetings summary counts by status.
- Seed: room names corrected (AR + EN).

Frontend (tx-os/pages/protocol.tsx, App.tsx, locales):
- URL-synced tabs under /protocol/:tab (slug<->tab maps).
- Dedicated Rooms tab (moved out of Gifts); RoomDialog isActive toggle.
- External approve/reject buttons + rejectionReason display; ExternalDialog
  create omits status, edit limited to completed/cancelled.
- Dashboard cards clickable to navigate; reports external section.
- Bookings list/calendar (grouped-by-day) view toggle.
- New i18n keys in ar.json + en.json.

Verification: tx-os + protocol.ts typecheck clean; drizzle push + re-seed done;
both workflows restarted; /api/protocol/me returns 401 unauth; architect
re-review approved. Pre-existing typecheck errors in executive-meetings.ts and
push.ts are unrelated and were not touched.

Note: had to rebuild lib/db declarations (tsc --build --force) so api-server
project references picked up the new schema columns.
2026-07-06 09:46:12 +00:00
Replit Agent 2322b77b8d Add Public Relations & Protocol module (Task #649)
New, fully separate app module "العلاقات العامة والمراسم / Public
Relations and Protocol". Does not touch executive-meetings; all new
tables are prefixed protocol_ and are additive-only, routes under
/protocol.

Includes:
- 6 protocol_ tables (rooms, room bookings, external meetings, gifts,
  gift issues, audit log) in lib/db.
- API routes + role-scoped middleware (protocol_super_admin, pr_manager,
  officer, requester, viewer) with requireProtocolAccess gating.
- Room-booking conflict prevention (overlap rule newStart<existingEnd
  AND newEnd>existingStart vs pending/approved) with AR error message.
- Gifts/shields (دروع) registry + issuance with stock tracking.
- Bilingual AR/EN frontend page (tabs + dialogs) at /protocol, app tile,
  i18n keys, and seed data (roles, permission, default rooms).

Concurrency/security hardening from code review:
- All role guards now validate users.isActive (inactive-user bypass fix).
- Per-room SELECT ... FOR UPDATE lock serializes booking check-then-write.
- Approve paths re-read inside the transaction and use state-conditional
  updates (WHERE ... AND status='pending' RETURNING).
- Gift issuance claims the issue conditionally, then does an atomic
  conditional stock decrement; failure rolls back the claim.

Verified: drizzle push (6 tables), seed, frontend + api-server typecheck
(only pre-existing errors in untouched files), unauth gating returns 401,
conflict predicate confirmed. Architect review PASS.
2026-07-06 09:25:40 +00:00
Replit Agent 4c291269f1 Center meeting and attendee information within table cells
Apply 'text-center' class directly to the table data (td) elements for meeting and attendee columns to ensure consistent alignment with the time column.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ddb4304d-b474-4cc9-adee-c11f7a7c1674
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fKZh36i
Replit-Helium-Checkpoint-Created: true
2026-07-05 12:37:08 +00:00
Replit Agent 0451d45110 Center meeting details and attendee information horizontally
Update `artifacts/tx-os/src/pages/executive-meetings.tsx` to apply horizontal centering to meeting titles, locations, attendee group headers, attendee names, and merge placeholders for improved visual consistency.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 611256d1-27a6-47df-99f9-aa1bdc1a3b15
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fKZh36i
Replit-Helium-Checkpoint-Created: true
2026-06-04 08:02:19 +00:00
Replit Agent ed838fd804 Task #646: Keep external meeting text navy, not red
Problem: When a meeting was flagged external, the row was tinted red
(pink bg + red number box — desired) but the title cell text and the
attendee list text were also forced to red (#b91c1c), which the user
did not want.

Change (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Meeting title cell: always use navy text (#0B1E3F) instead of switching
  to red on meeting.isExternal.
- Attendee list <ul>: always use navy text (#0B1E3F) instead of switching
  to red on externalText.
- Removed the now-dead `externalText` prop end-to-end (call-site value,
  type declaration, and destructured default) to keep typecheck clean.

Unchanged: the red row tint, red number box, and all external-meeting
behavior (e.g. auto-postpone rules). Internal/virtual meetings unaffected.

Verified: tx-os typecheck passes; HMR applied with no console errors.
2026-06-02 06:57:42 +00:00
Replit Agent ceadff6944 Improve notification deep-linking and add Arabic meeting reminders
Enhance notification handling to preserve deep-link parameters, add relatedId and relatedType to notifications for better association, and implement Arabic pluralization for meeting reminders.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 9abb57f5-4f2c-4eed-9fad-ab278885083e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fKZh36i
Replit-Helium-Checkpoint-Created: true
2026-06-01 14:48:26 +00:00
riyadhafraa 19fd5cc445 #641 Schedule cell polish: alignment + external text colour
- Attendees column no longer centres its content. AttendeeFlow's <ul>
  switched from justify-center to justify-start; AttendeeGroup header
  and subheading rows (both committed and pending) switched from
  text-center to text-start. Restores cell-start alignment (right in
  AR, left in EN) the user expected on fresh inline-add and on
  existing rows.
- External meetings now render the meeting title cell text and the
  attendee names in red (#b91c1c), matching the row tint family, in
  addition to the existing red background. Implemented by:
  * Conditional text-[#b91c1c] / text-[#0B1E3F] on the meeting <td>
    based on meeting.isExternal.
  * New optional `externalText` prop on AttendeeFlowSharedProps,
    threaded from AttendeesCell (derived from meeting.isExternal) and
    applied as a colour class on the AttendeeFlow root <ul> so all
    attendee names inherit red. Group headers and subheadings keep
    their neutral navy palette per scope.
- Non-external rows and the cancelled/highlighted white-on-red # cell
  are untouched. Print/PDF path reuses the same renderer so the red
  carries through automatically.
- No schema/API/migration changes. TS clean.

Files: artifacts/tx-os/src/pages/executive-meetings.tsx
2026-05-25 12:54:19 +00:00
riyadhafraa 32cac678bf #641 Schedule cell polish: alignment + external text colour
- Attendees column no longer centres its content. AttendeeFlow's <ul>
  switched from justify-center to justify-start; AttendeeGroup header
  and subheading rows (both committed and pending) switched from
  text-center to text-start. Restores cell-start alignment (right in
  AR, left in EN) the user expected on fresh inline-add and on
  existing rows.
- External meetings now render the meeting title cell text and the
  attendee names in red (#b91c1c), matching the row tint family, in
  addition to the existing red background. Implemented by:
  * Conditional text-[#b91c1c] / text-[#0B1E3F] on the meeting <td>
    based on meeting.isExternal.
  * New optional `externalText` prop on AttendeeFlowSharedProps,
    threaded from AttendeesCell (derived from meeting.isExternal) and
    applied as a colour class on the AttendeeFlow root <ul> so all
    attendee names inherit red. Group headers and subheadings keep
    their neutral navy palette per scope.
- Non-external rows and the cancelled/highlighted white-on-red # cell
  are untouched. Print/PDF path reuses the same renderer so the red
  carries through automatically.
- No schema/API/migration changes. TS clean.

Files: artifacts/tx-os/src/pages/executive-meetings.tsx
2026-05-25 12:51:46 +00:00
riyadhafraa eb25beec29 #639: Fix external-meeting toggle (save + layout)
Two issues reported on the new "اجتماع خارجي" toggle from #635:

1. "Doesn't save." Investigation: the server schema, PATCH/POST handlers,
   GET endpoint (full-row select), and DB column are all correct, and
   `docker/migrate.sh` runs `pnpm --filter @workspace/db run push-force`
   on every `docker compose up`, so the production schema does receive
   the `is_external` column. The likeliest real-world cause is a stale
   API image (column existed but the post-review API code that persists
   the flag wasn't deployed yet), or a transient stale-state render
   where `state.isExternal` was undefined and silently dropped from the
   PATCH body. Hardened the client so neither can happen again:
   - Both save sites (Manage dialog `save()` ~6700, inline Schedule
     edit save body ~3421) now send `Boolean(state.isExternal)` instead
     of the raw value, so an undefined state coerces to `false` rather
     than dropping the field.

2. "Overlaps other fields." The toggle row was sharing a row with the
   date input in the 2-col grid and used a fixed `h-9` wrapper that
   sat awkwardly next to the taller time pickers on narrow screens.
   - Made the FormRow `full` so it spans both columns on its own row,
     above the time pickers.
   - Dropped the fixed `h-9` and switched to `py-1` + `gap-3` + `text-sm`
     so it matches the form's vertical rhythm on iPhone, iPad portrait,
     iPad landscape, and desktop.

Out of scope / not changed
- No server changes (the server already accepts boolean | number and
  coerces to boolean; `Boolean(data.isExternal ?? false)` on POST and
  `if (data.isExternal !== undefined)` on PATCH are unchanged).
- No new badge / copy — that lives under separate proposed work.
- Postpone / cascade rules unchanged.
2026-05-25 12:19:48 +00:00
riyadhafraa 736785e3d3 #635: External meetings + auto-numbering
User asked for three things in the executive-meetings module:
1. Remove the "daily number" input from the add/edit dialog — numbering
   is auto-assigned by the server already, so the field was just noise.
2. Add an "اجتماع خارجي" (external meeting) toggle. When ON, the row
   is force-tinted red in the schedule grid.
3. External meetings must be excluded from the auto-postpone flow:
   the alert "Postpone" button is disabled, and the cascade shift
   skips them as followers.

Changes
- lib/db/src/schema/executive-meetings.ts
  + `is_external boolean not null default false` column. Pushed via
    drizzle-kit (no destructive migration; defaults backfill).
- artifacts/api-server/src/routes/executive-meetings.ts
  + `isExternal` added to base zod fields, POST insert, PATCH update.
  + POST/PATCH force `rowColor='red'` when `isExternal=true`.
  + POST /:id/postpone-minutes rejects external meetings with
    HTTP 409 + code `external_meeting_no_auto_postpone`.
  + `computeCascadeShift` filters out external followers (preview +
    writer stay in lockstep, as the comment promises).
- artifacts/tx-os/src/pages/executive-meetings.tsx
  + Meeting type + MeetingFormState gain `isExternal`; dropped the
    `dailyNumber` field from the form state entirely.
  + `emptyMeetingForm` / `openEdit` updated; ManageSection.save() and
    the inline ScheduleSection save body now send `isExternal` and
    never send `dailyNumber`.
  + MeetingFormDialog: removed the daily-number FormRow and added a
    Switch-based external-meeting FormRow with localized hint.
  + `rowColors` memo coerces to "red" whenever `m.isExternal`.
  + Audit-diff "interesting" list now includes `isExternal`.
- artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx
  + Meeting type gains optional `isExternal`.
  + "Postpone" button disabled + tooltip when meeting is external.
- locales: added `field.isExternal`, `field.isExternalHint`,
  `field.isExternalOn/Off`, and `alert.externalCannotPostpone` in
  both ar.json and en.json. Existing dailyNumber keys kept (still
  referenced by the manage-table column header / cell display).

Post-review hardening (from architect pass)
- PATCH now coerces rowColor → 'red' for any PATCH that touches a row
  that is (or becomes) external. Prevents PDF/export paths that read
  raw rowColor from showing a non-red tint for externals.
- Schedule row quick-actions popover: the "Postpone" button is now
  disabled with a localized tooltip when the meeting is external,
  matching the upcoming-meeting alert behavior.
- PostponeDialog.handleErr maps the 409 code
  `external_meeting_no_auto_postpone` to the localized
  `alert.externalCannotPostpone` toast so users see a real reason
  instead of the raw English error string.

Notes
- The `dailyNumber` column stays in DB (heavily used for slot
  persistence and the unique index); only the form input was removed.
  Server's `nextDailyNumber()` already auto-assigns when the field is
  absent on POST, and PATCH leaves it untouched when absent.
- Two pre-existing TS errors remain in api-server (`isHighlighted`
  type-inference quirk on `z.union(...).transform(...)` and a
  font_settings comparison) — not in scope for this task.
- Tests not added; verification by typecheck on changed surfaces and
  workflow-restart smoke. No e2e harness was wired.
2026-05-25 11:59:54 +00:00
riyadhafraa 305155a4fd Task #626: stop notifications after logout + tighten meeting reminder
- /auth/logout: delete all push_subscriptions rows for the user before
  destroying the session. Best-effort with logger.warn on failure so an
  operator can correlate any leaked-ring report to a real DB error.
- home.tsx handleLogout: call pushSub.disable() before the logout
  mutation, raced against a 1.5s timeout so a stalled service worker
  cannot block sign-out.
- executive-meeting-scheduler: switch the eligibility filter from
  denylist (ne cancelled + ne completed) to whitelist
  (eq status='scheduled'). Postponed / rescheduled / future statuses
  can no longer trigger false 5-minute reminders.
- docker-compose.yml: pin TZ=Asia/Riyadh on postgres, api, and web
  services so the scheduler's naive date/time math matches the
  operator's wall clock instead of UTC.

Gitea push failed with TLS error during this session — code committed
locally, needs manual `./scripts/publish-to-gitea.sh --push` retry
when the desktop-11cj93j tunnel recovers.
2026-05-23 08:25:04 +00:00
riyadhafraa b402138464 Remove global dialog adjustments for soft keyboard
Revert global CSS and JavaScript hooks that attempted to manage dialog positioning with the soft keyboard, which caused issues on desktop.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 3ea26578-1c32-4300-8404-7ae727e33444
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/qrzw3bH
Replit-Helium-Checkpoint-Created: true
2026-05-21 12:43:31 +00:00
riyadhafraa 338d80cf0a iOS keyboard fix v3 — visualViewport + touch-gate (#624)
Root causes the prior attempts missed:

1) Safari iOS/iPadOS ignores `interactive-widget=resizes-content`,
   so `100dvh` never shrinks when the soft keyboard opens. Every
   previous override that used `dvh` to size the dialog was sized
   to the full screen and ran behind the keyboard.

2) The CSS rule from #622 (`[role="dialog"]:has(:focus)`) was
   unconditional. On desktop, focusing any input inside a Radix
   DialogContent instantly stretched it to width:100vw + top:0,
   which collided with Radix's open-state slide-animation
   transforms and rendered the dialog as a broken narrow strip
   pinned in the top-left corner — visible in Tahani's laptop
   screenshot when opening "Add Meeting".

3) `AdminFormDialog` puts `role="dialog"` on a NON-fixed inner
   card, so the rule's `top/left/right` had no effect there
   anyway; the card stayed centred in a layout-viewport-sized
   wrapper which iOS does not shrink.

Fix:
- New `useVisualViewportVars` hook mounted in
  NotificationsSocketBridge. Writes `--vv-height` and
  `--vv-offset-top` on `<html>` from `window.visualViewport`,
  updated on resize / scroll / orientationchange. Falls back
  to `innerHeight`/0 if visualViewport is absent.
- Rewrote the index.css dialog rule:
  * Gated inside `@media (any-pointer: coarse)` so desktop is
    completely unaffected.
  * Uses `var(--vv-height, 100vh)` and `var(--vv-offset-top, 0px)`
    instead of `100dvh` / `0`, so dialogs actually track the
    visible band on iOS.
- New `.dialog-vv-wrapper` helper class that applies the same
  vv-sized top/height to bespoke full-screen overlay wrappers.
- Applied `.dialog-vv-wrapper` to AdminFormDialog and the admin
  ConfirmDialog wrappers; switched their `fixed inset-0` to
  `fixed inset-x-0 top-0 h-screen` and their inner cards from
  `max-h-[90vh]`/`max-h-[85vh]` to `max-h-full` so the card
  shrinks with the wrapper.

Kept `useScrollFocusedDialogField` unchanged — works correctly
once the dialog itself is sized to the visible band.

`tsc --noEmit` clean.
2026-05-21 12:04:12 +00:00
riyadhafraa 9e590b959e Unify edit-role dialog with AdminFormDialog (#621)
The "edit role" dialog in admin.tsx was hand-rolled as a bespoke
<div role-less wrapper> with its own close button, sticky footer and
full-width split buttons. That made it look different from every
other admin dialog (add role, add/edit group, add user), and on
iPad the action buttons floated mid-dialog because the content
exceeded max-h-[92vh]. The custom wrapper also lacked role="dialog",
so the new `:has(:focus)` CSS keyboard rule (index.css:492) never
applied to it.

Replaced the wrapper with AdminFormDialog (maxWidth="lg", KeyRound
icon, same title key). All inner content — name/desc fields, rename
warning, permissions list, removal impact, RolePermissionHistory,
RecentActivityForTarget — moved verbatim as children. Removed the
custom sticky footer; AdminFormDialog provides the unified footer
with cancel/save buttons. Preserved testIds `edit-role-dialog` and
`edit-role-submit`. Disabled logic translated 1:1 into
submitDisabled + isPending props (note: `impactError` coerced to
boolean via `!!` to satisfy the prop type).

No behavior change: same handlers (closeEditDialog, handleEdit),
same loading spinner, same disabled conditions. Bonus: the keyboard
override CSS now applies because AdminFormDialog already sets
role="dialog".

`tsc --noEmit` clean.
2026-05-21 08:57:08 +00:00
riyadhafraa bc47a08559 Improve drag and drop interactions by adjusting activation constraints
Adjusts drag activation constraints for pointer and touch sensors, enabling instant drag initiation in edit mode with a lower activation distance and no delay, while maintaining a higher threshold and delay outside of edit mode.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1b66c87b-bec3-4f9e-b161-b4789c48a6fa
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/qrzw3bH
Replit-Helium-Checkpoint-Created: true
2026-05-21 07:07:31 +00:00
riyadhafraa d31dfe5962 Improve layout alignment for form fields on smaller screens
Add `min-w-0` to `FormRow` component to prevent input fields from overlapping in RTL layouts and ensure proper column spanning on various screen sizes.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 5dabc2a6-6905-4d77-ba0d-f4edc783cb06
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/qrzw3bH
Replit-Helium-Checkpoint-Created: true
2026-05-20 14:57:31 +00:00
riyadhafraa d369447917 Drag-to-reorder service cards (iOS jiggle mode)
Long-press a service tile (~500 ms) on the Services page → grid enters
edit mode: every card jiggles, a floating "تم / Done" pill appears, and
cards become draggable via @dnd-kit. Drop on another tile to reorder.
Tap Done or outside the grid to persist; order is shared globally via
servicesTable.sortOrder.

Per the task default: admin-only. Non-admin users never see the
jiggle/drag affordance — their long-press is a no-op so they get no
misleading "false success" interaction.

Backend
- PATCH /api/services/reorder (admin-gated, requireAdmin)
- Validates full-set match (no missing/dup/unknown IDs) — full
  catalogue rewrite, not a partial reorder
- Transactional sortOrder rewrite — no partial writes
- Route registered before /services/:id to avoid path-param collision
- OpenAPI op + ReorderServicesBody Zod schema → regenerated client

Frontend
- artifacts/tx-os/src/pages/services.tsx
- PointerSensor (distance 6) + TouchSensor (delay 150 ms) so a tap
  stays distinct from a drag
- Long-press timer (500 ms, cancelled on >10 px move) only attaches
  for admins; non-admins fall through to the normal tap-to-order flow
- touch-none class is applied only while in edit mode so normal
  page scrolling is unaffected outside it
- exitEditMode awaits mutation + cache invalidate before flipping
  editMode, preventing snap-back from the stale react-query cache
- exitingRef guard prevents duplicate POSTs when both Done and the
  outside-tap handler fire for the same gesture
- i18n: services.editMode.{done,hint,saveFailed} in ar.json + en.json
2026-05-20 11:53:47 +00:00
riyadhafraa 376918f983 Drag-to-reorder service cards (iOS jiggle mode)
Long-press a service tile (~500 ms) → grid enters edit mode: every
card jiggles, a floating "تم / Done" pill appears, and cards become
draggable via @dnd-kit. Drop on another tile to reorder. Tap Done or
outside the grid to persist; the new order is shared globally via
servicesTable.sortOrder.

Backend
- POST /api/services/reorder (admin-gated, requireAdmin)
- Validates full-set match (no missing/dup/unknown IDs)
- Transactional sortOrder rewrite — no partial writes
- Route registered before /services/:id to avoid path-param collision
- OpenAPI op + ReorderServicesBody Zod schema → regenerated client

Frontend
- artifacts/tx-os/src/pages/services.tsx rewritten
- PointerSensor (distance 6) + TouchSensor (delay 150 ms) — long-press
  and tap-to-open stay distinct
- touch-none only applied while in edit mode so normal scrolling is
  unaffected outside it
- exitEditMode awaits mutation + invalidate before flipping editMode,
  preventing snap-back from stale react-query cache
- exitingRef guard against duplicate exit calls (Done button +
  outside-tap handler firing for the same gesture)
- Non-admins can enter edit mode (haptic affordance) but local reorder
  is silently reverted on exit
- i18n: services.editMode.{done,hint,saveFailed} added to ar.json/en.json

Pre-existing unrelated typecheck errors in push.ts and
executive-meeting-font-settings.ts are not touched by this change.
2026-05-20 11:51:28 +00:00
riyadhafraa b4bb5407c9 Improve usability and appearance of notes and meetings sections
Adjust styling for mobile responsiveness in the notes header, improve contrast for completed checklist items, and refine the layout of the executive meetings form dialog.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ce2e0a68-adc1-43df-936b-f4a7772ac303
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/IO8TMSC
Replit-Helium-Checkpoint-Created: true
2026-05-19 17:12:37 +00:00
riyadhafraa 6412759888 Update user role management and dialog designs
Remove order receiver role toggle, redesign role dialogs with improved styling and responsiveness, and unify timestamp formatting.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 25a76ee9-32c5-4ed2-b5f4-2ef38b94cced
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/IO8TMSC
Replit-Helium-Checkpoint-Created: true
2026-05-19 14:45:16 +00:00
riyadhafraa c0f4a84c93 Improve user role management by clarifying inherited permissions
Update the user role toggle to reflect direct role assignments, disable the toggle if the role is inherited, and display an inherited badge.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b813888e-26a9-4feb-8414-3c57fdc104d6
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/IO8TMSC
Replit-Helium-Checkpoint-Created: true
2026-05-19 14:14:52 +00:00
riyadhafraa 297f445ab9 Improve dialog design and responsiveness for all admin forms
Refactor AdminFormDialog component to use a solid white background, adjust responsive widths based on screen size, and enhance header styling for better clarity and appearance across devices.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ac415bb1-bc8b-4ad3-8424-7ce717528dde
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/eDmI6vt
Replit-Helium-Checkpoint-Created: true
2026-05-19 12:14:22 +00:00
riyadhafraa 0bcb2cd28f Improve audit log filtering with a searchable user list and better date formatting
Introduces a combobox for filtering audit logs by actor, replaces multiple date formatting calls with a dedicated helper function, and updates locale files for new filter UI elements.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 50672f01-2472-4869-a234-ecc811a41e0b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/eDmI6vt
Replit-Helium-Checkpoint-Created: true
2026-05-19 12:11:54 +00:00
riyadhafraa d17587e096 Remove Notifications tab from Executive Meetings (Task #616)
The Notifications tab inside Executive Meetings only ever rendered a
log of `meeting_created` fan-out rows (the only event type currently
wired), was filtered to the selected schedule date, and overlapped
with the Audit log. For real users it was almost always empty and
just took space in the tab strip — see attached iPhone screenshot in
the conversation. User asked explicitly to delete it.

Changes:

1. `artifacts/tx-os/src/pages/executive-meetings.tsx`
   - Removed the `{ key: "notifications", icon: Bell }` entry from
     the `SECTIONS` array so the tab no longer appears in the strip.
   - Removed the `case "notifications": return me.canRead;` branch
     from `isSectionVisible`.
   - Removed the `{section === "notifications" && …}` render branch
     and the `<NotificationsSection …/>` JSX.
   - Deleted the `NotificationsSection` component entirely (~90 lines)
     and the unused `NotificationRow` type.
   - Left the `Bell` import in place — still used by the bell button
     elsewhere in the page (line ~8580).

2. `artifacts/tx-os/src/locales/{ar,en}.json`
   - Removed the entire `executiveMeetings.notificationsPage` block
     (headers, status labels, type labels, empty state). All keys
     were verified to only be consumed by the just-deleted component.
   - Left `executiveMeetings.notifications.*` alone — those are the
     per-user preferences UI strings and are unrelated.

3. Backend left untouched on purpose:
   - `GET /api/executive-meetings/notifications` still exists.
   - `recordExecutiveMeetingNotifications` still runs on meeting
     create and still writes to `executive_meeting_notifications`.
   - The bell icon, push, and notification preferences UI are
     unaffected because they read `notificationsTable`, not the
     executive-meeting-specific table.

The orphaned `GET /executive-meetings/notifications` route now has
no frontend caller. I'm leaving it in this commit (it's harmless,
still permission-gated) and proposing a follow-up to remove it
cleanly instead of expanding scope here.

Code review: not run yet — will run after committing per the
standard flow.
2026-05-19 12:02:41 +00:00
riyadhafraa d09ffc874c Improve navigation and search responsiveness across different screen sizes
Update mobile layout for audit and manage sections, introduce horizontal scroll affordances with gradient fades for tabs, and adjust search input and heading sizes for better mobile usability.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 64d8dc15-5f85-4085-9e29-d417f1c980a0
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/eDmI6vt
Replit-Helium-Checkpoint-Created: true
2026-05-19 11:56:36 +00:00
riyadhafraa e00e015b8b Improve how user roles are managed and displayed on the admin page
Introduce separation of direct and inherited roles in user profiles and API responses. Modify the admin UI to disable the "order receiver" toggle when a role is inherited, providing a clearer user experience. Update API endpoints and schemas to reflect these changes, alongside locale updates for translated strings.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 33aee19f-8f0f-4399-98e0-39fe09a87e1b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/m92e9kU
Replit-Helium-Checkpoint-Created: true
2026-05-19 06:41:10 +00:00
riyadhafraa 918485411f Task #602: quick-note button in schedule fullscreen mode
The amber "ملاحظة / Note" button (#600) lives in the Executive
Meetings page header. The whole header is hidden in fullscreen, so the
quick-note panel — which itself already works in fullscreen — had no
entry point. User had to exit fullscreen, add the note, then re-enter.

Added a floating amber pill next to the existing fullscreen-exit pill
at the top-end of the page. Mirrors the header button exactly:
- Same handler (setQuickNoteOpen + setQuickNoteTrigger).
- Same gating: only Schedule section, hidden while the panel is open
  or while the bulk-actions toolbar is active.
- Same i18n key (executiveMeetings.quickNote.label) — no new strings.
- Same amber palette (bg-amber-100 / border-amber-300 / text-[#0B1E3F])
  with the Plus icon and label.
- Positioned at `top-3 end-14` so it sits inboard of the exit pill
  (`end-3`, w-9) in both LTR (to the left) and RTL (to the right),
  with no overlap.
- `print:hidden`, `data-testid="em-fullscreen-quicknote"`.

Single JSX addition (~36 lines incl. comment) in
artifacts/tx-os/src/pages/executive-meetings.tsx, right after the
exit-fullscreen button. No other files touched. No new state, no new
i18n, no API changes, no dependency changes. Non-fullscreen layout is
untouched. tx-os tsc passes.
2026-05-18 13:59:02 +00:00
riyadhafraa 4290bc54c9 Task #603: distinct, non-purple home-tile colours
The four default home-screen tiles (Notes / Services / Meetings /
Admin) showed up as three blue tiles + one orange one. Root cause was
NOT the DB — seed.ts already gave each app a distinct hex — but the
`gradientForColor()` mapper in `artifacts/tx-os/src/pages/home.tsx`
only recognised blue/purple/green/orange/pink/teal, so yellow
(#eab308), red (#ef4444), and the dark navy meetings colour all fell
through to the default blue gradient.

Changes:

1. `artifacts/tx-os/src/index.css` — added `.icon-tile-red` and
   `.icon-tile-yellow` gradients matching the existing tile style.

2. `artifacts/tx-os/src/pages/home.tsx` — `gradientForColor()` now
   maps red/dc2626 and yellow/facc15 first. Purple branch stays for
   admin-custom apps but no seeded row uses it.

3. `scripts/src/seed.ts` —
   - Meetings: `#0B1E3F` → `#22c55e` (green).
   - Notifications: `#8b5cf6` → `#06b6d4` (cyan) — kills the last
     purple in the default palette per user direction.

4. `scripts/src/update-app-colors.ts` (new) — idempotent migration
   that bumps existing rows ONLY when they still hold the exact old
   default colour, so admin customisations from the Apps editor are
   preserved. Wired into `scripts/package.json` as
   `pnpm run update-app-colors`.

5. `scripts/redeploy.sh` — new step 5/6 runs the colour migration
   after seed and before `docker compose up -d`, on the same one-shot
   `migrate` container pattern as other one-shot scripts.

No schema changes, no new deps. tx-os tsc passes, scripts tsc passes.
After `./scripts/redeploy.sh` on Mac, home shows four distinct hues
(yellow / orange / green / red) and no tile is purple anywhere.
2026-05-18 13:52:42 +00:00
riyadhafraa 475415b542 Update quick note button label to be shorter
Update the label for the quick note button in the executive meetings header from "Take a note..." to "Note" in English and "اكتب ملاحظة…" to "ملاحظة" in Arabic.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1f9a6661-3f64-45ae-b766-13143669519d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/m92e9kU
Replit-Helium-Checkpoint-Created: true
2026-05-18 13:01:59 +00:00
riyadhafraa 504a8d4a64 #600: move quick-note entry from floating side tab into meetings header
User reported the yellow vertical "اكتب ملاحظة…" tab pinned to the page
edge (left-0 top-[40vh]) felt out of place and asked for it to live
inside the Executive Meetings header in a clear spot.

Changes (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Added an amber-tinted Button in the header action cluster, placed
  before the Export PDF button so it reads first in RTL. Uses
  bg-amber-100 / border-amber-300 to keep the original yellow visual
  identity while matching the other header buttons' shape and size.
  Reuses the existing `executiveMeetings.quickNote.label` i18n key —
  no new translations. testid: `em-quick-note-header`.
- Same gating as the old floating tab: section === "schedule" &&
  !isFullscreen && !bulkToolbarActive && !quickNoteOpen, so the button
  hides while the composer is open, in fullscreen, during bulk
  selection, and on non-schedule sub-pages.
- Removed the `<MeetingsQuickNoteTab />` mount and the component
  definition itself. Updated the stale comment on `bulkToolbarActive`
  to refer to the header button.

Verified:
- `rg em-quick-note-tab|MeetingsQuickNoteTab` clean across repo (no
  tests referenced the old testid).
- `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.

No deviation from the plan.
2026-05-18 12:58:30 +00:00
riyadhafraa 65a50444f0 Rearrange header clock to display time vertically below date and weekday
Update the header clock component to a vertical layout, displaying weekday, date, and then time.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 88ce34d5-ccee-404b-a0e0-7e07876438f9
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/m92e9kU
Replit-Helium-Checkpoint-Created: true
2026-05-18 12:54:49 +00:00
riyadhafraa 5dcf949173 Update meeting header to show live digital time
Replace analog clock with a live digital clock display using useNow and formatTimeI18n.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b1d23dc1-fa04-4c00-95be-572d8f7be20b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/m92e9kU
Replit-Helium-Checkpoint-Created: true
2026-05-18 12:52:39 +00:00
riyadhafraa 46b1d59466 #599 fullscreen-exit button: icon only, no text label
In executive-meetings.tsx, the floating exit-fullscreen control was
a pill (`gap-1.5 px-3 py-1.5`) showing both a Minimize2 arrow and
the Arabic/English label "الخروج من ملء الشاشة" / "Exit fullscreen".
User asked for icon only.

Changes:
- Removed the `<span>{t(...)}</span>` label.
- Swapped pill classes for a circular icon button: `inline-flex
  items-center justify-center rounded-full bg-[#0B1E3F] text-white
  w-9 h-9 shadow-lg hover:bg-[#0B1E3F]/90 print:hidden`, keeping the
  same `fixed top-3 end-3 z-50` positioning.
- Bumped the icon to `w-5 h-5` so it reads cleanly on its own.
- Added a `title` attribute (hover tooltip) mirroring the existing
  `aria-label` — screen readers + mouse users still get the
  localised "exit fullscreen" affordance.
- `data-testid="em-fullscreen-exit"` untouched, so existing tests
  keep passing.
- No translation key changes; no change to the enter-fullscreen
  toolbar button at L2958.

Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
2026-05-18 12:34:39 +00:00
riyadhafraa df2559f7cb #598 global "other apps" bottom dock + remove duplicate #596 gear
New shared component `artifacts/tx-os/src/components/app-dock.tsx`:
takes a `currentSlug` prop, pulls the apps list from the same
`useListApps()` query Home uses, filters out the current app and any
inactive ones, and renders a floating centered pill near the bottom
with one circular tinted button per remaining app. Reuses Home's
`resolveIcon(name)` pattern (Lucide string -> component, falls back to
Grid2X2) and Home's openApp logic (internal -> setLocation, external
_tab -> window.open, external_iframe -> /embedded/:id). Renders
nothing when fewer than 1 other apps are available. Hidden in print,
respects safe-area-inset-bottom, scrollable on narrow widths.

Mounted on every app page except Home:
- executive-meetings.tsx (currentSlug="executive-meetings", matches
  seed slug; hidden when isFullscreen is true so the schedule fills
  the screen unobstructed).
- services.tsx (currentSlug="services")
- notes.tsx (currentSlug="notes")
- orders-incoming.tsx (currentSlug="orders-incoming" — not a built-in
  app slug, so all apps show, which is the right UX since incoming
  inbox is reached via the topbar, not the home grid).
- admin.tsx (currentSlug="admin")

Removed the two gear/back-arrow blocks added by #596 in
executive-meetings.tsx (test ids em-president-settings,
em-president-back-to-schedule) — the dock now provides cross-app
navigation including Admin/Settings. The L902 force-schedule effect
that snaps the president back to the schedule section outside of
schedule|settings is left in place; it still gates the hidden
Manage / Audit tabs.

Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No backend, no new translations, Home untouched.
2026-05-18 12:31:16 +00:00
riyadhafraa b43357439c #598 global "other apps" bottom dock + remove duplicate #596 gear
New shared component `artifacts/tx-os/src/components/app-dock.tsx`:
takes a `currentSlug` prop, pulls the apps list from the same
`useListApps()` query Home uses, filters out the current app and any
inactive ones, and renders a floating centered pill near the bottom
with one circular tinted button per remaining app. Reuses Home's
`resolveIcon(name)` pattern (Lucide string -> component, falls back to
Grid2X2) and Home's openApp logic (internal -> setLocation, external
_tab -> window.open, external_iframe -> /embedded/:id). Renders
nothing when fewer than 1 other apps are available. Hidden in print,
respects safe-area-inset-bottom, scrollable on narrow widths.

Mounted on every app page except Home:
- executive-meetings.tsx (currentSlug="executive-meetings", matches
  seed slug; hidden when isFullscreen is true so the schedule fills
  the screen unobstructed).
- services.tsx (currentSlug="services")
- notes.tsx (currentSlug="notes")
- orders-incoming.tsx (currentSlug="orders-incoming" — not a built-in
  app slug, so all apps show, which is the right UX since incoming
  inbox is reached via the topbar, not the home grid).
- admin.tsx (currentSlug="admin")

Removed the two gear/back-arrow blocks added by #596 in
executive-meetings.tsx (test ids em-president-settings,
em-president-back-to-schedule) — the dock now provides cross-app
navigation including Admin/Settings. The L902 force-schedule effect
that snaps the president back to the schedule section outside of
schedule|settings is left in place; it still gates the hidden
Manage / Audit tabs.

Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No backend, no new translations, Home untouched.
2026-05-18 12:30:06 +00:00
riyadhafraa 264f52827d #598 global "other apps" bottom dock + remove duplicate #596 gear
New shared component `artifacts/tx-os/src/components/app-dock.tsx`:
takes a `currentSlug` prop, pulls the apps list from the same
`useListApps()` query Home uses, filters out the current app and any
inactive ones, and renders a floating centered pill near the bottom
with one circular tinted button per remaining app. Reuses Home's
`resolveIcon(name)` pattern (Lucide string -> component, falls back to
Grid2X2) and Home's openApp logic (internal -> setLocation, external
_tab -> window.open, external_iframe -> /embedded/:id). Renders
nothing when fewer than 1 other apps are available. Hidden in print,
respects safe-area-inset-bottom, scrollable on narrow widths.

Mounted on every app page except Home:
- executive-meetings.tsx (currentSlug="executive-meetings", matches
  seed slug; hidden when isFullscreen is true so the schedule fills
  the screen unobstructed).
- services.tsx (currentSlug="services")
- notes.tsx (currentSlug="notes")
- orders-incoming.tsx (currentSlug="orders-incoming" — not a built-in
  app slug, so all apps show, which is the right UX since incoming
  inbox is reached via the topbar, not the home grid).
- admin.tsx (currentSlug="admin")

Removed the two gear/back-arrow blocks added by #596 in
executive-meetings.tsx (test ids em-president-settings,
em-president-back-to-schedule) — the dock now provides cross-app
navigation including Admin/Settings. The L902 force-schedule effect
that snaps the president back to the schedule section outside of
schedule|settings is left in place; it still gates the hidden
Manage / Audit tabs.

Verified `pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
No backend, no new translations, Home untouched.
2026-05-18 12:28:16 +00:00
riyadhafraa c80b1ce085 #596 صلاحية الرئيس: حذف بحث الاجتماعات + زر الإعدادات
Two changes to artifacts/tx-os/src/pages/executive-meetings.tsx,
both gated on `isPresidentView` so non-president users are unaffected.

1. Schedule search hidden for the President
   - Wrapped <SearchToggle> in ScheduleSection (L2945) in
     {!isPresidentView && (...)}.
   - Kept searchQuery state + meetingMatchesSearch wiring intact:
     when query is "" the filter is a no-op, so the President sees
     the full day's meetings, and the box reappears for anyone else.

2. Gear button so the President can reach Settings (font controls)
   - Added an icon-only gear button (SettingsIcon, lucide-react —
     already imported) in the header right-side block (L1113),
     visible only when isPresidentView && section === "schedule".
     data-testid="em-president-settings".
   - Added an icon+label "back to schedule" button (ArrowRight for
     RTL / ArrowLeft for LTR) shown when isPresidentView &&
     section === "settings". data-testid="em-president-back-to-schedule".
   - Relaxed the force-schedule effect (L966) so the President is
     only redirected when section is neither "schedule" nor
     "settings" — otherwise the gear tap would bounce back instantly.
   - Sub-nav stays hidden (`!isPresidentView` on the SECTIONS map
     is unchanged). Gear is the only entry point.
   - No backend, no new i18n keys (reuses
     executiveMeetings.nav.settings / .schedule).
   - FontSettingsSection already renders for canRead users, and
     canEditGlobalFontSettings stays as the API reports it — no
     extra client-side gate.

`pnpm --filter @workspace/tx-os exec tsc --noEmit` clean.
2026-05-18 11:36:01 +00:00
riyadhafraa 28547e2a7e Hide the centered clock on small screens to prevent overlap
Hide the absolutely positioned HeaderClock on mobile viewports to prevent it from overlapping with the header title and action buttons, restoring it on larger screens.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 425e9ff3-23ed-48ac-9baa-e63cfcba6efa
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/m92e9kU
Replit-Helium-Checkpoint-Created: true
2026-05-18 11:20:21 +00:00
riyadhafraa cfba4fe9e9 #591 meetings: reuse the real notes Composer (not a custom textarea)
The yellow side tab on /executive-meetings previously opened a bespoke
textarea panel I added in #590. The user pointed out (screenshot) that
it should be the exact same composer card they use on /notes — yellow
background, color picker, label menu, checklist toggle, "حفظ" Save,
paper-plane Send.

Changes:
- Extracted Composer + its inline helpers (ChecklistEditor, KindToggle,
  ColorPicker, LabelMenu, newItemId) out of pages/notes.tsx into a new
  shared module at components/notes/composer.tsx.
- Extracted SendNoteDialog out of pages/notes.tsx into a new shared
  module at components/notes/send-note-dialog.tsx.
- pages/notes.tsx now imports both modules; behavior of the notes page
  is unchanged.
- pages/executive-meetings.tsx: deleted InlineQuickNotePanel. The
  schedule view now renders <Composer folderSelection={{kind:"all"}}>
  inside the yellow side-tab toggle, plus <SendNoteDialog> for the
  paper-plane Send flow. A new quickNoteTrigger counter is bumped each
  time the tab opens so the shared Composer auto-expands and focuses
  its textarea. A small X in the corner dismisses the panel; Save just
  collapses the composer (matches /notes UX), Send saves then opens
  the recipient picker.
- Notes created from the meetings panel land as unfiled notes in the
  user's notes (folder "all"), visible immediately in /notes.
- Composer gained an optional onClose prop fired from its reset()
  path (save / outside-click save / send success / empty reset).
  /notes doesn't pass it (composer stays mounted, just collapses).
  The meetings page wires it to setQuickNoteOpen(false) so the panel
  dismisses and the yellow side tab returns after every save path,
  not just the X / Send paths. Addresses code-review feedback.

Notes:
- No locale-key cleanup needed: the old quickNote.placeholder/send
  keys lived only as t() fallbacks, not in en.json/ar.json.
- pnpm --filter @workspace/tx-os exec tsc --noEmit: clean.
- HMR errors visible in the browser console were from the intermediate
  syntax-error state during extraction; cleared after the final edit.
2026-05-18 11:17:26 +00:00
riyadhafraa fe9ee04333 #591 meetings: reuse the real notes Composer (not a custom textarea)
The yellow side tab on /executive-meetings previously opened a bespoke
textarea panel I added in #590. The user pointed out (screenshot) that
it should be the exact same composer card they use on /notes — yellow
background, color picker, label menu, checklist toggle, "حفظ" Save,
paper-plane Send.

Changes:
- Extracted Composer + its inline helpers (ChecklistEditor, KindToggle,
  ColorPicker, LabelMenu, newItemId) out of pages/notes.tsx into a new
  shared module at components/notes/composer.tsx.
- Extracted SendNoteDialog out of pages/notes.tsx into a new shared
  module at components/notes/send-note-dialog.tsx.
- pages/notes.tsx now imports both modules; behavior of the notes page
  is unchanged.
- pages/executive-meetings.tsx: deleted InlineQuickNotePanel. The
  schedule view now renders <Composer folderSelection={{kind:"all"}}>
  inside the yellow side-tab toggle, plus <SendNoteDialog> for the
  paper-plane Send flow. A new quickNoteTrigger counter is bumped each
  time the tab opens so the shared Composer auto-expands and focuses
  its textarea. A small X in the corner dismisses the panel; Save just
  collapses the composer (matches /notes UX), Send saves then opens
  the recipient picker.
- Notes created from the meetings panel land as unfiled notes in the
  user's notes (folder "all"), visible immediately in /notes.

Notes:
- No locale-key cleanup needed: the old quickNote.placeholder/send
  keys lived only as t() fallbacks, not in en.json/ar.json.
- pnpm --filter @workspace/tx-os exec tsc --noEmit: clean.
- HMR errors visible in the browser console were from the intermediate
  syntax-error state during extraction; cleared after the final edit.
2026-05-18 11:15:22 +00:00
riyadhafraa bf81debebf Task #590: Daily meetings — clock, inline notes, fullscreen header
Three small UX fixes on /executive-meetings (schedule tab):

1) HeaderClock: dropped the digital "HH:MM:SS م" string next to the
   weekday — the analog dial already conveys the live time, so it was
   redundant. Bumped AnalogClock size from 36 to 44 so the dial reads
   cleanly without the digital crutch. Weekday + date stay (calendar
   info, not clock info). Removed the now-unused `useState`/`useEffect`
   ticker inside HeaderClock — AnalogClock has its own.

2) "اكتب ملاحظة" yellow side tab no longer navigates away to /notes.
   Added a transient `quickNoteOpen` local state in
   ExecutiveMeetingsPageInner. The tab's onClick now toggles it on; a
   new `InlineQuickNotePanel` is rendered above <ScheduleSection> with
   an auto-focused textarea, Cancel/Send buttons, Cmd/Ctrl+Enter to
   submit, Esc to close. Submit POSTs to /api/notes with the existing
   contract ({ content, color: "yellow" }) and closes on success. The
   side tab hides while the panel is open so they don't double up.
   State auto-resets when the user leaves the schedule tab.

3) Fullscreen exit pill overlap: the floating "الخروج من ملء الشاشة"
   pill (fixed top-3 end-3) was covering the schedule table's "الوقت"
   column header. Added `pt-12 sm:pt-14` to the fullscreen branch of
   <main> so the table starts below the pill on iPad landscape
   (1024-wide). Non-fullscreen layout unchanged.

i18n: added executiveMeetings.quickNote.{placeholder,send} in EN+AR;
existing executiveMeetings.quickNote.label reused for the panel
header. All keys also have inline fallbacks so missing translations
don't break.

Drive-by: fixed a TypeScript narrowing error in admin.tsx left over
from the prior task's review-comment cleanup (data.startedAt is
already typed as string, so the typeof check produced `never` in the
else branch — replaced with a direct call).

Verified: pnpm --filter @workspace/tx-os exec tsc --noEmit passes
clean, Vite HMR updated cleanly in dev.
2026-05-18 11:04:27 +00:00