Commit Graph

2211 Commits

Author SHA1 Message Date
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
Riyadh 637f4410b2 Transitioned from Plan to Build mode 2026-07-06 11:05:30 +00:00
Replit Agent 6c5ae9d437 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: 34b757e2-71fa-4d1c-8840-e8c61b98d0f2
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 11:05:30 +00:00
Riyadh ec7b1b7af2 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.
2026-07-06 10:13:41 +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
Riyadh 159a044c24 Task #649: Public Relations & Protocol module (bookings, external meetings, gifts)
Separate additive module, fully independent from executive-meetings. All tables
prefixed protocol_, routes under /protocol, Arabic-first RTL UI.

This session: addressed code-review rejection by switching protocol
authorization from hardcoded role-name checks to scoped permission checks.
- auth.ts: requireProtocolAccess now guards on the "protocol.access" permission
  (same permission that gates the home-screen tile via app_permissions), so
  backend read access aligns with tile visibility.
- protocol.ts: replaced role-name helpers with makeRequirePerm + PROTOCOL_PERMS
  map (protocol.access / request / mutate / approve / rooms.manage / audit.read).
  Booking auto-approve and /protocol/me capabilities now derive from permissions.
- seed.ts: seeds all 6 scoped permissions and maps them to admin + the 5 protocol
  roles (access:6, request:5, mutate:4, approve:3, audit:3, rooms:2 role grants).

Verified: typecheck clean (tx-os, api-server protocol/auth, scripts), seed applied,
DB mappings confirmed, /api/protocol/me returns 401 unauth. Architect review PASS.

Deviations: none from spec. Frontend already consumed capability flags from /me,
so no frontend changes were needed for the permission switch.
2026-07-06 09:54:30 +00:00
Replit Agent 5fe5194349 Task #649: Public Relations & Protocol module (bookings, external meetings, gifts)
Separate additive module, fully independent from executive-meetings. All tables
prefixed protocol_, routes under /protocol, Arabic-first RTL UI.

This session: addressed code-review rejection by switching protocol
authorization from hardcoded role-name checks to scoped permission checks.
- auth.ts: requireProtocolAccess now guards on the "protocol.access" permission
  (same permission that gates the home-screen tile via app_permissions), so
  backend read access aligns with tile visibility.
- protocol.ts: replaced role-name helpers with makeRequirePerm + PROTOCOL_PERMS
  map (protocol.access / request / mutate / approve / rooms.manage / audit.read).
  Booking auto-approve and /protocol/me capabilities now derive from permissions.
- seed.ts: seeds all 6 scoped permissions and maps them to admin + the 5 protocol
  roles (access:6, request:5, mutate:4, approve:3, audit:3, rooms:2 role grants).

Verified: typecheck clean (tx-os, api-server protocol/auth, scripts), seed applied,
DB mappings confirmed, /api/protocol/me returns 401 unauth. Architect review PASS.

Deviations: none from spec. Frontend already consumed capability flags from /me,
so no frontend changes were needed for the permission switch.
2026-07-06 09:54:30 +00:00
Riyadh 2f84bd2c6f 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 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
Riyadh 874d49c439 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 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 62470c580d 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: e4960c19-0ee6-410c-95a5-24eefd66c0b7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/sVsVYJx
Replit-Helium-Checkpoint-Created: true
2026-07-06 08:59:30 +00:00
Replit Agent bb0ecaa9f9 Saved progress at the end of the loop
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 353dbbfd-1386-43a2-879c-a2aa2797108e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/sVsVYJx
Replit-Helium-Checkpoint-Created: true
2026-07-05 13:20:41 +00:00
Replit Agent 72f5526866 Add a live server for development and testing purposes
Install the srt-live-server package as a dependency in replit.nix.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 36525074-60aa-4921-9b2e-78a74f24bd4f
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 13:11:31 +00:00
Riyadh 7ee4c0858a 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.
2026-07-05 12:37:08 +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
Riyadh 7f81eb01b2 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.
2026-06-04 08:02:19 +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
Riyadh bb3ac7cc1c Merge Gitea; keep Replit newer work 2026-06-03 20:24:13 +00:00
Riydx0 4ac864b520 Merge Gitea; keep Replit newer work 2026-06-03 20:24:13 +00:00
Riyadh 34f969178e Create a script to easily sync local work with the Gitea server
Add a new shell script to automate fetching, merging, and pushing changes to the Gitea remote repository.
2026-06-03 20:14:00 +00:00
Replit Agent 83ba0e9671 Create a script to easily sync local work with the Gitea server
Add a new shell script to automate fetching, merging, and pushing changes to the Gitea remote repository.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 66d30e41-f995-49c8-bcc0-9c5fdebac0ab
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-03 20:14:00 +00:00
Riyadh 92de2e374e Transitioned from Plan to Build mode 2026-06-03 20:02:36 +00:00
Replit Agent 3b089240a8 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: 2a9cedca-ef8e-4ed9-bae9-47655545586e
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-03 20:02:36 +00:00
Riyadh f189399fbf Transitioned from Plan to Build mode 2026-06-02 07:00:27 +00:00
Replit Agent 686b63a983 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: 04f098df-c7dd-4afd-97d7-4442ccb82097
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-02 07:00:27 +00:00
Riyadh 487304344d 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 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
Riyadh 9821f7af1c Transitioned from Plan to Build mode 2026-06-02 06:56:14 +00:00
Replit Agent 365c446ef8 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: 311b34a9-81a5-4dbb-98f5-79aa9c1ac575
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-02 06:56:14 +00:00
Riyadh 348db18bcd 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.
2026-06-01 14:48:26 +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
Riyadh 3c1b3107e3 Transitioned from Plan to Build mode 2026-06-01 14:30:10 +00:00
Replit Agent 7651a23372 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: d63d9f16-3017-41bb-ba30-093a81c0dd10
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/2UyHOi5
Replit-Helium-Checkpoint-Created: true
2026-06-01 14:30:10 +00:00
Riyadh 0c35fbe395 #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 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
Riyadh 6f74fa1045 #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 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
Riyadh 776fd8460e Transitioned from Plan to Build mode 2026-05-25 12:45:26 +00:00
riyadhafraa a131b2dc75 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: a1009a04-06b8-4ae1-9bb4-ba5a718c50c0
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/2UyHOi5
Replit-Helium-Checkpoint-Created: true
2026-05-25 12:45:26 +00:00
Riyadh b177f653e6 #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 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
Riyadh bc2b648cc4 Transitioned from Plan to Build mode 2026-05-25 12:18:23 +00:00
riyadhafraa d82f37b46c 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: c15f0cec-032c-4934-8d7b-1541df865fc6
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/2UyHOi5
Replit-Helium-Checkpoint-Created: true
2026-05-25 12:18:23 +00:00
Riyadh 7bfb4afd1a #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 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
Riyadh 2e6f02fe83 Transitioned from Plan to Build mode 2026-05-25 11:44:31 +00:00
riyadhafraa 5dc42fdb81 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: e83fea25-962d-4eb9-95bd-d2f39525752a
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/gIKdbC8
Replit-Helium-Checkpoint-Created: true
2026-05-25 11:44:31 +00:00
Riyadh 7b56f0fe18 Task #634: fix SendNoteDialog centering on iPad PWA with keyboard open
User-reported regression from #630: opening "Send note" on iPad PWA
showed the recipient list pushed to the left side of the screen,
narrower than designed, with the dialog title + search input clipped
off the top. Dismissing the keyboard restored normal centered layout.

Root cause: the `keyboardFallback` branch (iPad PWA, where
visualViewport doesn't update) set `top: "6vh"` in *layout viewport*
coordinates. On iPad PWA Safari, opening the keyboard scrolls the
page down behind the keyboard, so layout-viewport `6vh` ends up
above the visible visual viewport — clipping the top half of the
dialog. Separately, the inline `transform` replaced Tailwind's
translate-x/y on Radix's content, and without an explicit `left:50%`
the animation/RTL combination shifted the box left.

Fix (artifacts/tx-os/src/components/notes/send-note-dialog.tsx):
- `keyboardFallback`: `top: calc(${vv.offsetTop || 0}px + 6vh)` so the
  anchor follows the visible visual viewport when Safari scrolls the
  page behind the keyboard. Add explicit `left: 50%`. Add
  `overflowY: auto` so if dialog content exceeds 50dvh, the dialog
  scrolls internally instead of clipping the header.
- `keyboardActive` branch: also add explicit `left: 50%` defensively
  for the same animation/RTL reason (no behavior change on devices
  where it was already working, but eliminates the asymmetry).

No changes to the shared `dialog.tsx`, composer, or executive-meetings
dialog — scope strictly limited to the regression site.

TypeScript: clean. Pre-existing errors in api-server/src/routes/push.ts
unrelated to this diff.
2026-05-25 11:36:29 +00:00
riyadhafraa 65bfe32e60 Task #634: fix SendNoteDialog centering on iPad PWA with keyboard open
User-reported regression from #630: opening "Send note" on iPad PWA
showed the recipient list pushed to the left side of the screen,
narrower than designed, with the dialog title + search input clipped
off the top. Dismissing the keyboard restored normal centered layout.

Root cause: the `keyboardFallback` branch (iPad PWA, where
visualViewport doesn't update) set `top: "6vh"` in *layout viewport*
coordinates. On iPad PWA Safari, opening the keyboard scrolls the
page down behind the keyboard, so layout-viewport `6vh` ends up
above the visible visual viewport — clipping the top half of the
dialog. Separately, the inline `transform` replaced Tailwind's
translate-x/y on Radix's content, and without an explicit `left:50%`
the animation/RTL combination shifted the box left.

Fix (artifacts/tx-os/src/components/notes/send-note-dialog.tsx):
- `keyboardFallback`: `top: calc(${vv.offsetTop || 0}px + 6vh)` so the
  anchor follows the visible visual viewport when Safari scrolls the
  page behind the keyboard. Add explicit `left: 50%`. Add
  `overflowY: auto` so if dialog content exceeds 50dvh, the dialog
  scrolls internally instead of clipping the header.
- `keyboardActive` branch: also add explicit `left: 50%` defensively
  for the same animation/RTL reason (no behavior change on devices
  where it was already working, but eliminates the asymmetry).

No changes to the shared `dialog.tsx`, composer, or executive-meetings
dialog — scope strictly limited to the regression site.

TypeScript: clean. Pre-existing errors in api-server/src/routes/push.ts
unrelated to this diff.
2026-05-25 11:36:29 +00:00