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.
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.
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.
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.
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
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.
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.
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.
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
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.
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
Introduces a new public-facing API endpoint and UI for submitting room booking requests without authentication, including rate limiting and input validation.
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.
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.
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.
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.
Apply 'text-center' class directly to the table data (td) elements for meeting and attendee columns to ensure consistent alignment with the time column.
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.
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.