620ab284e254f4d7fd91f951e76950a209dc7b59
948 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
620ab284e2 |
fix(executive-meetings): quick-actions UX + reschedule/cancel polish (Task #563)
User-reported polish on the executive-meetings quick-actions flow on iPad/mobile. Six fixes, all client-side: 1. Quick-actions surface now opens from ANY click on a meeting row (was: only the attendees cell). Root cause: handleRowClick guard skipped any `em-edit-*`, `em-merge-edit-*`, and `em-time-*` descendants. Those affordances only do anything in edit mode, but handleRowClick already early-returns when edit mode is on, so the testid guard was always over-aggressive. Dropped those guards plus the `[role='button']` rule from the interactive selector, kept the real `button`/`input`/`textarea`/`a`/`menuitem`/`checkbox`/etc. guards and the `em-row-actions-*` / `em-row-select-*` testid guards (those affordances are visible outside edit mode and must not trigger the surface). File: artifacts/tx-os/src/pages/executive-meetings.tsx (~L4350). 2. Originating row is now unmistakable on iPad: quickOpenShadow is `inset 0 0 0 4px highlight, 0 0 0 2px highlight` (was a single 3px inset) and the row background tints to `rgba(highlight, 0.08)` while quick-actions is open. File: artifacts/tx-os/src/pages/executive-meetings.tsx (~L4287, L4344). 3. Postpone dialog header was overlapping the Radix close X in RTL. Added `pr-8` on the DialogHeader (same pattern already used for the row-quick mini-dialog). File: artifacts/tx-os/src/components/executive-meetings/ upcoming-meeting-alert.tsx (~L1447). 4. Reschedule tab: date / start / end inputs collided on iPad portrait. Grid is now `grid-cols-1 md:grid-cols-3` (single column through iPad portrait, three across on landscape/desktop), with `min-w-0` on each cell and `w-full` on every Input so Safari's native date/time pickers stop overflowing. Same file (~L1761). 5. Cancel tab X overlap is resolved by the same DialogHeader fix in step 3 (one Dialog wraps all three tabs). 6. Removed the `cancelHint` paragraph + the `cancelHint` translation keys in ar.json (L1252) and en.json (L1164) — fully unused now. NOT pushed to Gitea (Tailscale to desktop-11cj93j still flaky). Pending pushes: #560 (VAPID), #561 (CEO roles), #563 (this). |
||
|
|
5834036707 |
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: d8f29dc1-570e-487c-8034-8fc616ca80dd Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fNoqq4C Replit-Helium-Checkpoint-Created: true |
||
|
|
0f71ae4102 |
feat(executive-meetings): grant executive_ceo mutate + audit + push notify (Task #561)
User request: السماح للرئيس (executive_ceo) بإنشاء/تعديل/تأجيل/حذف
الاجتماعات، استلام إشعارات Push للتذكير، والاطلاع على سجل التدقيق —
مع إبقاء إعدادات الخطوط/الشعار حصرية على مدير المكتب التنفيذي.
Changes (3 lines added, backend-only):
1. artifacts/api-server/src/routes/executive-meetings.ts
- Added "executive_ceo" to MUTATE_ROLES (line 120) → POST/PATCH/DELETE
on meetings now accepted for the CEO role.
- Added "executive_ceo" to ADMIN_AUDIT_ROLES (line 130) → /audit
endpoint now reachable; canViewAudit=true in the bootstrap payload.
- EM_ADMIN_ROLES intentionally untouched, so font-settings and global
PDF brand assets remain admin + executive_office_manager only.
2. artifacts/api-server/src/lib/executive-meeting-scheduler.ts
- Added "executive_ceo" to EM_NOTIFY_ROLES (line 18) → CEO users with
an active Web Push subscription now receive the LEAD_MINUTES=5
pre-meeting reminder push.
Frontend requires no changes: it gates buttons on canMutate /
canViewAudit booleans returned by the bootstrap endpoint (lines
617-619), which flip to true automatically for the CEO role.
Verification: API workflow restarted clean, scheduler started OK,
/api/executive-meetings and /alert-state respond 304/200.
NOT pushed to Gitea: Tailscale link to desktop-11cj93j is still down
from this sandbox (HTTP=000). Task #560's earlier VAPID fix
(
|
||
|
|
42b881355b |
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: 0cdfc11a-cdea-4382-82a8-d1997a8d0e3d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fNoqq4C Replit-Helium-Checkpoint-Created: true |
||
|
|
629069b1fe |
Fix VAPID subject fallback for push notifications
Update push notification logic to correctly handle missing VAPID_SUBJECT environment variables by using logical OR operator instead of nullish coalescing for subject fallback. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 96637a28-4f37-4966-8adb-95f8f66ee769 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fNoqq4C Replit-Helium-Checkpoint-Created: true |
||
|
|
9fa40f00ab |
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: b520895c-b60d-4b2b-a96d-62499515c77d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/97ZFaoX Replit-Helium-Checkpoint-Created: true |
||
|
|
c1e3a81cae |
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: 679db8c7-6a6d-410a-b781-5112c524b87d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/97ZFaoX Replit-Helium-Checkpoint-Created: true |
||
|
|
51c3d8a8ba |
Fix toggle switch behavior in right-to-left reading modes
Update the switch component's CSS to correctly position the toggle thumb in RTL layouts for both checked and unchecked states. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ee6229a6-5472-4ca0-8f84-74b229703438 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/97ZFaoX Replit-Helium-Checkpoint-Created: true |
||
|
|
f4253d4d9d |
Improve toggle switch behavior in right-to-left languages
Add custom RTL and LTR variants to Tailwind CSS configuration to correctly position toggle switch indicators in Arabic and other right-to-left languages. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 84451adc-9c86-40d5-80ba-846e7ea2a813 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/97ZFaoX Replit-Helium-Checkpoint-Created: true |
||
|
|
05446afb5d |
Improve meeting reminders with new scheduler and improved push notifications
Add a new background scheduler to trigger meeting reminders, implement atomic claims for push notifications to prevent duplicates, and add an `ignoreConnected` option to push notifications. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a63187bd-4947-4e1f-8959-e8d68ff96a8c Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/97ZFaoX Replit-Helium-Checkpoint-Created: true |
||
|
|
55d19298e7 |
Add bilingual support for notifications and automatic HTTPS
Implement bilingual text for push notifications, allowing users to receive them in their preferred language. Automatically configure HTTPS with Let's Encrypt for custom domains to ensure persistent subscriptions. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 94f06aee-a5cf-45df-979e-f940cce77214 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/yEFtMKN Replit-Helium-Checkpoint-Created: true |
||
|
|
d78818b88a |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Four new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe`, and a spec-aligned alias `DELETE /api/push/subscribe?endpoint=...` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`, gated on `serviceWorker` AND `PushManager` support so older browsers no-op cleanly. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - New `artifacts/api-server/tests/push-410-unit.test.ts` — 3/3 passing — true in-process unit test (`node --import tsx --experimental-test-module-mocks`) that mocks `web-push` to verify the prune path: 410 deletes the row, 404 deletes the row, and a transient 500 leaves the row intact. `tsx` added as a devDep and the test:run script picks up `*.test.ts` alongside the existing `.mjs` suites. - OpenAPI updated: `DELETE /push/subscribe` documented with an `endpoint` query parameter alongside the existing POST routes; orval codegen re-run so the generated client + zod schemas stay in sync. - Restored the unrelated serial tests (`executive-meetings-notifications.test.mjs`, `setup-wizard.test.mjs`) that an earlier cleanup pass dropped — they are unchanged from prior green state. - Architect rounds addressed: 1. race + payload size. 2. dedup gate + first-launch UX + SW base-path + initial tests. 3. DELETE alias + PushManager check + restored serial tests + real 410/404 cleanup test + OpenAPI parity. 4. .env.docker.example + replit.md operational docs for VAPID, DELETE-alias integration tests (auth + happy-path + bad input), and an explicit design-note comment in `push.ts` documenting the intentional user-wide dedup policy. |
||
|
|
b5720c9aac |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Four new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe`, and a spec-aligned alias `DELETE /api/push/subscribe?endpoint=...` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`, gated on `serviceWorker` AND `PushManager` support so older browsers no-op cleanly. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - New `artifacts/api-server/tests/push-410-unit.test.ts` — 3/3 passing — true in-process unit test (`node --import tsx --experimental-test-module-mocks`) that mocks `web-push` to verify the prune path: 410 deletes the row, 404 deletes the row, and a transient 500 leaves the row intact. `tsx` added as a devDep and the test:run script picks up `*.test.ts` alongside the existing `.mjs` suites. - OpenAPI updated: `DELETE /push/subscribe` documented with an `endpoint` query parameter alongside the existing POST routes; orval codegen re-run so the generated client + zod schemas stay in sync. - Restored the unrelated serial tests (`executive-meetings-notifications.test.mjs`, `setup-wizard.test.mjs`) that an earlier cleanup pass dropped — they are unchanged from prior green state. - Architect rounds addressed: 1. race + payload size. 2. dedup gate + first-launch UX + SW base-path + initial tests. 3. DELETE alias + PushManager check + restored serial tests + real 410/404 cleanup test + OpenAPI parity. |
||
|
|
54ca20a00f |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Four new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe`, and a spec-aligned alias `DELETE /api/push/subscribe?endpoint=...` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`, gated on `serviceWorker` AND `PushManager` support so older browsers no-op cleanly. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - New `artifacts/api-server/tests/push-410-unit.test.ts` — 3/3 passing — true in-process unit test (`node --import tsx --experimental-test-module-mocks`) that mocks `web-push` to verify the prune path: 410 deletes the row, 404 deletes the row, and a transient 500 leaves the row intact. `tsx` added as a devDep and the test:run script picks up `*.test.ts` alongside the existing `.mjs` suites. - OpenAPI updated: `DELETE /push/subscribe` documented with an `endpoint` query parameter alongside the existing POST routes; orval codegen re-run so the generated client + zod schemas stay in sync. - Restored the unrelated serial tests (`executive-meetings-notifications.test.mjs`, `setup-wizard.test.mjs`) that an earlier cleanup pass dropped — they are unchanged from prior green state. - Architect rounds addressed: 1. race + payload size. 2. dedup gate + first-launch UX + SW base-path + initial tests. 3. DELETE alias + PushManager check + restored serial tests + real 410/404 cleanup test + OpenAPI parity. |
||
|
|
0740971a96 |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Four new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe`, and a spec-aligned alias `DELETE /api/push/subscribe?endpoint=...` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`, gated on `serviceWorker` AND `PushManager` support so older browsers no-op cleanly. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - New `artifacts/api-server/tests/push-410-unit.test.ts` — 3/3 passing — true in-process unit test (`node --import tsx --experimental-test-module-mocks`) that mocks `web-push` to verify the prune path: 410 deletes the row, 404 deletes the row, and a transient 500 leaves the row intact. `tsx` added as a devDep and the test:run script picks up `*.test.ts` alongside the existing `.mjs` suites. - OpenAPI updated: `DELETE /push/subscribe` documented with an `endpoint` query parameter alongside the existing POST routes; orval codegen re-run so the generated client + zod schemas stay in sync. - Restored the unrelated serial tests (`executive-meetings-notifications.test.mjs`, `setup-wizard.test.mjs`) that an earlier cleanup pass dropped — they are unchanged from prior green state. - Architect rounds addressed: 1. race + payload size. 2. dedup gate + first-launch UX + SW base-path + initial tests. 3. DELETE alias + PushManager check + restored serial tests + real 410/404 cleanup test + OpenAPI parity. |
||
|
|
bf39a6eda6 |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Four new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe`, and a spec-aligned alias `DELETE /api/push/subscribe?endpoint=...` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`, gated on `serviceWorker` AND `PushManager` support so older browsers no-op cleanly. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - Architect rounds addressed in order: 1. race + payload size — fixed. 2. dedup gate + first-launch UX + SW base-path + tests — fixed. 3. DELETE alias + PushManager check — fixed in this commit. A true 410-cleanup test was attempted but reaching the prune branch from an out-of-process .mjs test requires mocking the `web-push` module, which the current `node --test` + bundled-dist harness doesn't support cleanly; the 404/410 catch branch is small, self-contained, and was code-reviewed. Tracked as a follow-up. |
||
|
|
8f3b750961 |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes. - **De-dup gate:** skips push when the user has any active Socket.IO connection (uses `io.in(\`user:\${uid}\`).fetchSockets()`), so a connected user only gets the in-app chime, never a duplicate system notification. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (shared device) so the previous user's notifications can't leak. - Three new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe` (auth-gated, Zod-validated). - Push hooked into 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick only (no asset caching). All URLs (icon, badge, navigation target) resolved against `self.registration.scope`, so the SW works at root or under a subpath without code changes. - SW registration in `main.tsx` scoped to `BASE_URL`. - `use-push-subscription` hook (enable/disable/refresh + status). - New `PushEnablePrompt` card mounted in `App` — appears on first launch when supported + permission still "default", one-tap enable, dismiss persists for 14 days. Silent on unsupported devices. - `PushToggleRow` added inside Notification Settings. - ar/en strings: `notifSettings.push.*` and `common.later`. Plumbing - OpenAPI: 3 new operations under `notifications`; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the key; subscribe endpoint 401s without auth. - New `artifacts/api-server/tests/push.test.mjs` — 7/7 passing — covers VAPID endpoint, auth gating on subscribe/unsubscribe, row persistence + removal, idempotent re-subscribe with key rotation, account-switch endpoint reassignment, and malformed-input rejection. - Two architect rounds: first flagged race + payload size (fixed), second flagged dedup gate + first-launch UX + SW base-path + tests (all fixed in this commit). |
||
|
|
243ccb3e00 |
feat(push): add Web Push (VAPID) for lock-screen notifications on iPad PWA
Task #554. Adds a full Web Push stack on top of the existing Socket.IO notification fan-out so the iPad PWA (and any installed browser) receives system notifications when Tx OS is closed or backgrounded. Backend - New `push_subscriptions` table (userId + unique endpoint + p256dh/auth keys + ua + timestamps), exported from `@workspace/db`. - `artifacts/api-server/src/lib/push.ts`: - VAPID bootstrap from env, else cached file at LOCAL_STORAGE_ROOT (Docker volume) with /tmp fallback for Replit dev, else ephemeral in-memory. - `sendPushToUser()` honours `notificationsMuted` + per-channel prefs (orders/meetings/notes), prunes 404/410 endpoints, truncates payload bodies to ~3500 bytes so over-sized notes don't kill delivery. - `upsertSubscription()` deletes a stale row first when the same browser endpoint flips to a different user (account switch on shared device) so the previous user's notifications can't leak. - Three new routes: `GET /api/push/vapid-public-key`, `POST /api/push/subscribe`, `POST /api/push/unsubscribe` (auth-gated, Zod-validated). - Push hooked into the 4 existing emit sites: service-orders `notifyUser`, notes new-note + reply, executive-meeting broadcast. Frontend - `artifacts/tx-os/public/sw.js`: push + notificationclick handlers only (no asset caching). Focuses an existing tab or opens a new one at the payload URL. - SW registration in `main.tsx` scoped to `BASE_URL`. - `use-push-subscription` hook (enable/disable/refresh + status: unsupported / denied / default / subscribed). - New `PushToggleRow` in `NotificationSettingsContent` with ar/en strings (notifSettings.push.*). iPad copy explains that the user must add to Home Screen first for iOS to allow Web Push. Plumbing - OpenAPI: 3 new operations under `notifications` tag; orval codegen run. - `docker-compose.yml` passes VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT through to the api service. - `pnpm --filter @workspace/db run push` applied the schema. - web-push + @types/web-push installed in api-server. Verification - API restarts clean; `/api/push/vapid-public-key` returns the generated key; subscribe endpoint 401s without auth as expected. - Architect review surfaced two HIGH issues (account-switch leak, payload size); both fixed before completion. |
||
|
|
d91a68bc68 |
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: c32c85bc-ba42-427b-945b-c189acf14d71 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
e9d7b8dc76 |
Fix service images + Arabic ي dots on home tiles (Task #552)
Two visible bugs reported from the iPad after Task #551 deploy: 1) Only "قهوة سعودي" showed its image on /services. Three other seeded services (شاي، بلاك كوفي، عصير طازج) showed a broken-image placeholder because the seed only set `imageUrl` for Saudi Coffee. 2) The dots under final ي in the home-grid tile label "خدماتي" didn't render on iOS Safari. The label `<span>` didn't pin a font-family, so iOS fell back to a Latin face that lacks proper Arabic glyphs. Changes: - scripts/src/seed.ts: • Added `imageUrl` for tea/black-coffee/juice in the insert array (covers fresh installs). • Added a backfill loop AFTER the insert that fills `imageUrl` on existing rows only when the value is currently NULL. Admin- customized image URLs are never overwritten — matches the existing legacy-rename pattern at lines 395-423. - artifacts/tx-os/src/pages/home.tsx (AppIconContent label): • Added `lang` and `dir` attributes on the tile `<span>`. • Pinned an explicit font stack: DIN Next LT Arabic → Helvetica Neue LT Arabic → Tajawal for Arabic; Helvetica Neue → system-ui for English. The three Arabic faces are already declared in custom-fonts.css. No schema changes, no new dependencies. Verified typecheck passes and HMR reloaded both files cleanly in the dev server. Push to Gitea is a separate follow-up (git commit is restricted in the main agent; platform commits this task's changes on completion, and the push task can then mirror them to the Mac). |
||
|
|
51a401b6e5 |
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: e49c87fa-b03f-4bb0-8b1e-7d0c9ef599fb Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
66f063a978 |
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: 4285357b-389f-4cc3-b20e-906440f5c155 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
a6d5988421 |
Add PWA manifest + icons so iPad opens Tx OS full-screen (Task #550)
Problem: on the iPad the Safari URL bar stayed visible even after
"Add to Home Screen" because index.html had no PWA manifest and no
Apple PWA meta tags — the home-screen icon opened a normal Safari tab.
Changes:
- New `artifacts/tx-os/public/manifest.webmanifest`:
name/short_name "Tx OS", start_url "/", scope "/",
display "standalone", background_color and theme_color "#0B1E3F"
(matches the Meetings brand), and 3 icons (192/512 "any" + 512
"maskable" with safe-area padding).
- Generated icon PNGs from the existing `public/favicon.svg` brand
mark via ImageMagick:
public/icons/icon-192.png (192x192)
public/icons/icon-512.png (512x512)
public/icons/icon-maskable-512.png (512x512, 360x360 logo
centered on #0B1E3F so iOS/
Android masks don't crop)
public/apple-touch-icon.png (180x180, iPad/iPhone home)
- `artifacts/tx-os/index.html` head: added <link rel="manifest">,
<link rel="apple-touch-icon">, plus apple-mobile-web-app-capable,
mobile-web-app-capable, apple-mobile-web-app-status-bar-style
(default), apple-mobile-web-app-title, and theme-color meta tags.
- `replit.md`: new "iPad / iPhone — open Tx OS full-screen" section
with the one-time Add-to-Home-Screen steps and the gotcha that
pre-existing shortcuts must be removed and re-added to pick up the
manifest.
Verified: dev server returns 200 for /manifest.webmanifest,
/apple-touch-icon.png, and /icons/icon-192.png. The new files all
live under public/ so they're copied verbatim into the nginx image
on the next docker build.
|
||
|
|
b69bcd4241 |
Improve deployment script reliability and add explicit seed step
Update redeploy.sh to include an explicit seed step and adjust script numbering and help output. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ca6e30a4-b773-4707-8e0c-a72cb5bddf70 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
3c8bfbf84d |
Document Docker rebuild after git pull (Task #549)
Root cause of "service images don't show on the Mac after pulling latest": the SPA is baked into the nginx (`web`) image and the API binary is baked into the `api` image at `docker compose build` time. Running just `git pull && docker compose up -d` reuses the existing images, so new static assets in `artifacts/tx-os/public/` (and any front-end / API code changes) never reach the browser. Changes: - New `scripts/redeploy.sh` — single-command redeploy. Steps: 1. git pull --ff-only (skip with --no-pull) 2. docker compose build api web 3. docker compose run --rm migrate (drizzle push + idempotent seed) 4. docker compose up -d Detects `docker compose` v2 vs legacy `docker-compose`, runs from any cwd via BASH_SOURCE, set -euo pipefail, --help prints the header. chmod +x. Verified `bash -n` and `--help`. - `replit.md` — new "Redeploying after `git pull` (Docker hosts)" section that calls out the gotcha and points at the script. - `DEPLOYMENT_MIGRATION.md` — one-line cross-reference at the top of the deploy section so anyone reading deploy docs finds the script. No code or runtime behavior changed. No follow-ups proposed: the only natural next step (PWA full-screen) is already tracked as Task #550. |
||
|
|
46ac734a8e |
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: 7b9229d0-2fbc-4a67-a10e-a4461d2c4823 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
92425aafe6 |
Update internal notes order to display correctly
Adjust the sort order for internal notes to ensure proper display sequence. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 418871f8-1a80-43b8-948e-252be1122ed8 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
097276f8cc |
Task #545: Add Notes app to seed + rename Executive Meetings → Meetings
Why:
On the user's deployed instance the home screen was missing the Notes
tile (notes was in BUILTIN_APP_SLUGS but had no row in the seed's apps
array, so no apps row existed in the DB and the launcher had nothing
to render). The Executive Meetings tile also showed the long name
"إدارة الاجتماعات التنفيذية" / "Executive Meetings"; user asked for
the shorter "الاجتماعات" / "Meetings".
Changes (scripts/src/seed.ts):
- Added "notes" entry to the apps array (slug=notes, route=/notes,
iconName=StickyNote, color=#eab308, sortOrder=2, isActive=true,
nameAr="الملاحظات", nameEn="Notes").
- Renamed executive-meetings entry: nameAr/En + descriptions shortened.
- Extended the existing post-insert UPDATE for executive-meetings to
also push the new name + descriptions, so already-deployed instances
pick up the rename on re-seed (insert is onConflictDoNothing).
- Added a notes.access permission gate mirroring executive_meetings.access:
the permission is created, granted to the admin role only, and linked
to the notes app via app_permissions. getVisibleAppsForUser hides the
tile from anyone without notes.access — admins grant it from the
admin panel per role/user.
Why a permission gate (deviation from initial plan):
Code review caught that without app_permissions, getVisibleAppsForUser
shows every active app to non-admins by default, so a bare INSERT would
have made Notes visible to everyone — opposite of what the user asked
for ("only for those I choose"). Used the existing executive_meetings
pattern to keep the model consistent.
Also added replit.md documenting the workflows, the seed re-run step
after git pull, and the per-permission grant flow for restricted apps.
Verified: ran the seed; SQL confirms notes row + الاجتماعات rename +
notes.access restricted to admin role only.
|
||
|
|
9eab759acb |
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: 20a01eb7-fdd9-426a-90e4-3dbbc503b2e5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
c69e6903c1 | Delete .canvas/assets/asset_2133256110.png | ||
|
|
86b264f20a | Delete .canvas/assets/asset_556433877.png | ||
|
|
2679096e63 | chore: add dev workflows for tx-os + api-server | ||
|
|
b10ee5392c |
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: 1194403f-51ef-43ba-9710-3d091b4840f6 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/T9daF3x Replit-Helium-Checkpoint-Created: true |
||
|
|
66e0e5697f |
Task #542: fix api-server test deadlocks/races (all 327+13 tests green)
- executive-meetings.ts: add lockMeetingDate(executor, date)
pg_advisory_xact_lock helper. Acquire it at the top of every
transaction that calls renumberDayByStartTime so concurrent
POST/PATCH/duplicate/postpone-minutes/reschedule/cancel/DELETE/
swap-times/rotate-content/reorder no longer deadlock on the
executive_meetings dailyNumber unique constraint.
- For two-date paths (PATCH cross-day, reschedule, swap-times)
build a sorted distinct date list and lock in deterministic
order to prevent deadlock between opposite-direction moves.
- executive-meeting-notify.ts: add `.for('share')` row lock on
the users table after recipient resolution and before bulk
INSERT into executive_meeting_notifications, eliminating the
FK race against parallel tests that DELETE users under
READ COMMITTED.
- Move tests/executive-meetings-notifications.test.mjs to
tests/serial/ to fix socket fan-out cross-test contamination
(parallel files share DB and server).
No tests were weakened or skipped; all fixes live in
route/service code.
|
||
|
|
2889148a0e |
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: f5807abb-6742-4351-91e4-87cc3f7286c8 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
a5a35ef217 |
Improve test execution by automatically starting and stopping the API server
Refactor test execution to use a new script that manages the API server lifecycle, including building, starting, waiting for health, running tests, and shutting down, with support for using an existing server. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a42bd3b2-3145-4118-87aa-336a7e2189ca Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
77f8096deb |
Improve security and deployment flexibility for external access
Update README and application configurations to support reverse proxy setups, including TLS termination and proper cookie handling, and add an option to seed demo meetings. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: c15da2be-cee7-4cbb-8d58-f9fcc3c38ed7 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
fa31fb6374 |
task #539: push deployment-hardening branch to Gitea
Pushed the existing main-branch commits (since |
||
|
|
a01e102a08 |
Update documentation to clarify CORS error impact on user login
Update README.md with more specific language regarding CORS errors caused by incorrect ALLOWED_ORIGINS configuration, affecting the user login experience. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 201c4794-de42-4660-9fb4-83343d5bda24 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
7d13e8e345 |
deployment: harden self-hosted install behind HTTPS reverse proxies
Task #538. Fixes the four bugs that bit the user's first Tailscale-fronted install of Tx OS on their Mac: 1. Missing `user_sessions` table on fresh installs — `connect-pg-simple` now uses `createTableIfMissing: true`. Drizzle migrations still skip the table (`tablesFilter: ["!user_sessions"]`), so ownership stays with the session store. 2. Session cookie silently dropped behind HTTPS proxies that don't forward `X-Forwarded-Proto` (Tailscale serve, ngrok free, some Cloudflare Tunnels). New `TRUST_PROXY_HTTPS=true` env var injects the header before pino + session middleware, and `cookie.secure` is now derived from `PUBLIC_BASE_URL.startsWith("https://") || trustProxyHttps` instead of `NODE_ENV`. 3. `ALLOWED_ORIGINS` was undocumented for non-localhost URLs — added examples (LAN IP, Tailscale name, custom domain) in `.env.example`, `.env.docker.example`, `README.md`, plus a new "Reverse proxy / Tailscale / external URL" README section. 4. Demo executive meetings always seeded on real installs — gated behind `SEED_DEMO_MEETINGS=true` (default off), wired through `docker-compose.yml` to the migrate service. Architect (code review) approved; added a security note clarifying that `TRUST_PROXY_HTTPS=true` requires the API to be unreachable directly over plain HTTP. The git commit + push to Gitea is delegated to a follow-up task because main-agent sandbox blocks `git commit` / branch creation. |
||
|
|
f16f4763df |
Improve handling of external proxy configurations and session security
Introduces `TRUST_PROXY_HTTPS` environment variable and updates session cookie security logic in `app.ts`. Modifies `README.md` and `docker-compose.yml` for clarity on proxy configurations. Updates `seed.ts` to conditionally seed demo meetings based on `SEED_DEMO_MEETINGS` environment variable. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 097c59e1-8bbe-4846-a79e-3e661e6645c4 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
e0a4652947 |
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: 6a631a61-6adb-4703-8cb4-9afd32d0a7ad Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
e71ab51541 |
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: 75c6d3ef-cc1b-4fb0-ba39-fff6815eb92f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
154c223d8c |
Update documentation to include setup scripts and remove server type references
Refactors the README.md file to detail installation scripts and replace "VPS" with more general server terminology. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a4f33306-93f4-413c-b24b-58e08633a127 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/FvHcc7z Replit-Helium-Checkpoint-Created: true |
||
|
|
87a62f8089 |
feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535). Backend - New system_settings table (id=1 singleton): installed flag, base_url, local_domain, local_ip, https_mode, app_version. Pushed to dev DB. - New /api/setup/status (open) and /api/setup/{validate,complete} (gated by requireSetupOpen — 409 once installed). - completeInstall is fully transactional: pg_advisory_xact_lock serializes concurrent callers, double-gates on installed flag and admin existence, then atomically creates the admin user, assigns admin role + Admins/Everyone groups, and flips system_settings to installed=true. Rolls back on any failure. - Added redirectIfSetupNeeded() helper returning the full SetupStatus payload alongside a redirect target for SPA routing decisions. - Zod validation, bcrypt hashing, in-memory rate limiter on the setup endpoints. Backward compat - scripts/src/seed.ts now branches on installed flag + admin existence + SEED_*_PASSWORD env vars. Legacy installs (admin exists, system_settings empty) get backfilled to installed=true via ON CONFLICT DO UPDATE so they are never forced through the wizard. When env passwords are unset and no admin exists, the seed prints a wizard hint instead of seeding. Infra - docker-compose.yml: replaced nginx edge with a Caddy service that mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no longer publishes a port directly — Caddy is the only public ingress. - Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when HTTPS_MODE=skip so a fresh host without mkcert can still boot. - docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with WebSocket upgrade preserved and an HTTP→HTTPS redirect. - start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so the legacy http://localhost:${APP_PORT} URL keeps working. In local/byo mode it prints the https://${LOCAL_DOMAIN} URL. - .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT, HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional. Tooling - scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert, mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN). Tests - artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass. - scripts/tests/local-setup.test.mjs: 2/2 pass. Constraints honored: no force-push, no destructive ops, start.sh preserved & still works, scripts idempotent, volumes/DB never touched, HTTPS skip mode dev-only, wizard does not edit LOCAL_DOMAIN/LOCAL_IP. Out of scope / not addressed: pre-existing TS errors in routes/users.ts and pre-existing failure in executive-meetings-postpone-race.test.mjs. |
||
|
|
ca1508b04f |
feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535). Backend - New system_settings table (id=1 singleton): installed flag, base_url, local_domain, local_ip, https_mode, app_version. Pushed to dev DB. - New /api/setup/status (open) and /api/setup/{validate,complete} (gated by requireSetupOpen — 409 once installed). - completeInstall is fully transactional: pg_advisory_xact_lock serializes concurrent callers, double-gates on installed flag and admin existence, then atomically creates the admin user, assigns admin role + Admins/Everyone groups, and flips system_settings to installed=true. Rolls back on any failure. - Added redirectIfSetupNeeded() helper returning the full SetupStatus payload alongside a redirect target for SPA routing decisions. - Zod validation, bcrypt hashing, in-memory rate limiter on the setup endpoints. Backward compat - scripts/src/seed.ts now branches on installed flag + admin existence + SEED_*_PASSWORD env vars. Legacy installs (admin exists, system_settings empty) get backfilled to installed=true via ON CONFLICT DO UPDATE so they are never forced through the wizard. When env passwords are unset and no admin exists, the seed prints a wizard hint instead of seeding. Infra - docker-compose.yml: replaced nginx edge with a Caddy service that mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no longer publishes a port directly — Caddy is the only public ingress. - Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when HTTPS_MODE=skip so a fresh host without mkcert can still boot. - docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with WebSocket upgrade preserved and an HTTP→HTTPS redirect. - start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so the legacy http://localhost:${APP_PORT} URL keeps working. In local/byo mode it prints the https://${LOCAL_DOMAIN} URL. - .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT, HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional. Tooling - scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert, mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN). Tests - artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass. - scripts/tests/local-setup.test.mjs: 2/2 pass. Constraints honored: no force-push, no destructive ops, start.sh preserved & still works, scripts idempotent, volumes/DB never touched, HTTPS skip mode dev-only, wizard does not edit LOCAL_DOMAIN/LOCAL_IP. Out of scope / not addressed: pre-existing TS errors in routes/users.ts and pre-existing failure in executive-meetings-postpone-race.test.mjs. |
||
|
|
dba320fe35 |
feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535). Backend - New system_settings table (id=1 singleton): installed flag, base_url, local_domain, local_ip, https_mode, app_version. Pushed to dev DB. - New /api/setup/status (open) and /api/setup/{validate,complete} (gated by requireSetupOpen — 409 once installed). - completeInstall is fully transactional: pg_advisory_xact_lock serializes concurrent callers, double-gates on installed flag and admin existence, then atomically creates the admin user, assigns admin role + Admins/Everyone groups, and flips system_settings to installed=true. Rolls back on any failure. - Added redirectIfSetupNeeded() helper returning the full SetupStatus payload alongside a redirect target for SPA routing decisions. - Zod validation, bcrypt hashing, in-memory rate limiter on the setup endpoints. Backward compat - scripts/src/seed.ts now branches on installed flag + admin existence + SEED_*_PASSWORD env vars. Legacy installs (admin exists, system_settings empty) get backfilled to installed=true via ON CONFLICT DO UPDATE so they are never forced through the wizard. When env passwords are unset and no admin exists, the seed prints a wizard hint instead of seeding. Infra - docker-compose.yml: replaced nginx edge with a Caddy service that mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no longer publishes a port directly — Caddy is the only public ingress. - Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when HTTPS_MODE=skip so a fresh host without mkcert can still boot. - docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with WebSocket upgrade preserved and an HTTP→HTTPS redirect. - start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so the legacy http://localhost:${APP_PORT} URL keeps working. In local/byo mode it prints the https://${LOCAL_DOMAIN} URL. - .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT, HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional. Tooling - scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert, mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN). Tests - artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass. - scripts/tests/local-setup.test.mjs: 2/2 pass. Constraints honored: no force-push, no destructive ops, start.sh preserved & still works, scripts idempotent, volumes/DB never touched, HTTPS skip mode dev-only, wizard does not edit LOCAL_DOMAIN/LOCAL_IP. Out of scope / not addressed: pre-existing TS errors in routes/users.ts and pre-existing failure in executive-meetings-postpone-race.test.mjs. |
||
|
|
1b2567f3e4 |
feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535). Backend - New system_settings table (id=1 singleton): installed flag, base_url, local_domain, local_ip, https_mode, app_version. Pushed to dev DB. - New /api/setup/status (open) and /api/setup/{validate,complete} (gated by requireSetupOpen — 409 once installed). - completeInstall is fully transactional: pg_advisory_xact_lock serializes concurrent callers, double-gates on installed flag and admin existence, then atomically creates the admin user, assigns admin role + Admins/Everyone groups, and flips system_settings to installed=true. Rolls back on any failure. - Added redirectIfSetupNeeded() helper returning the full SetupStatus payload alongside a redirect target for SPA routing decisions. - Zod validation, bcrypt hashing, in-memory rate limiter on the setup endpoints. Backward compat - scripts/src/seed.ts now branches on installed flag + admin existence + SEED_*_PASSWORD env vars. Legacy installs (admin exists, system_settings empty) get backfilled to installed=true via ON CONFLICT DO UPDATE so they are never forced through the wizard. When env passwords are unset and no admin exists, the seed prints a wizard hint instead of seeding. Infra - docker-compose.yml: replaced nginx edge with a Caddy service that mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no longer publishes a port directly — Caddy is the only public ingress. - Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when HTTPS_MODE=skip so a fresh host without mkcert can still boot. - docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with WebSocket upgrade preserved and an HTTP→HTTPS redirect. - start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so the legacy http://localhost:${APP_PORT} URL keeps working. In local/byo mode it prints the https://${LOCAL_DOMAIN} URL. - .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT, HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional. Tooling - scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert, mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN). Tests - artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass. - scripts/tests/local-setup.test.mjs: 2/2 pass. Constraints honored: no force-push, no destructive ops, start.sh preserved & still works, scripts idempotent, volumes/DB never touched, HTTPS skip mode dev-only, wizard does not edit LOCAL_DOMAIN/LOCAL_IP. Out of scope / not addressed: pre-existing TS errors in routes/users.ts and pre-existing failure in executive-meetings-postpone-race.test.mjs. |
||
|
|
f6ff70cd2d |
feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535). Backend - New system_settings table (id=1 singleton): installed flag, base_url, local_domain, local_ip, https_mode, app_version. Pushed to dev DB. - New /api/setup/status (open) and /api/setup/{validate,complete} (gated by requireSetupOpen — 409 once installed). - completeInstall is fully transactional: pg_advisory_xact_lock serializes concurrent callers, double-gates on installed flag and admin existence, then atomically creates the admin user, assigns admin role + Admins/Everyone groups, and flips system_settings to installed=true. Rolls back on any failure. - Added redirectIfSetupNeeded() helper returning the full SetupStatus payload alongside a redirect target for SPA routing decisions. - Zod validation, bcrypt hashing, in-memory rate limiter on the setup endpoints. Backward compat - scripts/src/seed.ts now branches on installed flag + admin existence + SEED_*_PASSWORD env vars. Legacy installs (admin exists, system_settings empty) get backfilled to installed=true via ON CONFLICT DO UPDATE so they are never forced through the wizard. When env passwords are unset and no admin exists, the seed prints a wizard hint instead of seeding. Infra - docker-compose.yml: replaced nginx edge with a Caddy service that mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no longer publishes a port directly — Caddy is the only public ingress. - Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when HTTPS_MODE=skip so a fresh host without mkcert can still boot. - docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with WebSocket upgrade preserved and an HTTP→HTTPS redirect. - start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so the legacy http://localhost:${APP_PORT} URL keeps working. In local/byo mode it prints the https://${LOCAL_DOMAIN} URL. - .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT, HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional. Tooling - scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert, mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN). Tests - artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass. - scripts/tests/local-setup.test.mjs: 2/2 pass. Constraints honored: no force-push, no destructive ops, start.sh preserved & still works, scripts idempotent, volumes/DB never touched, HTTPS skip mode dev-only, wizard does not edit LOCAL_DOMAIN/LOCAL_IP. Out of scope / not addressed: pre-existing TS errors in routes/users.ts and pre-existing failure in executive-meetings-postpone-race.test.mjs. |
||
|
|
340017beaa |
feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535). Backend - New system_settings table (id=1 singleton): installed flag, base_url, local_domain, local_ip, https_mode, app_version. Pushed to dev DB. - New /api/setup/status (open) and /api/setup/{validate,complete} (gated by requireSetupOpen — 409 once installed). - completeInstall is fully transactional: pg_advisory_xact_lock serializes concurrent callers, double-gates on installed flag and admin existence, then atomically creates the admin user, assigns admin role + Admins/Everyone groups, and flips system_settings to installed=true. Rolls back on any failure. - Added redirectIfSetupNeeded() helper returning the full SetupStatus payload alongside a redirect target for SPA routing decisions. - Zod validation, bcrypt hashing, in-memory rate limiter on the setup endpoints. Backward compat - scripts/src/seed.ts now branches on installed flag + admin existence + SEED_*_PASSWORD env vars. Legacy installs (admin exists, system_settings empty) get backfilled to installed=true via ON CONFLICT DO UPDATE so they are never forced through the wizard. When env passwords are unset and no admin exists, the seed prints a wizard hint instead of seeding. Infra - docker-compose.yml: replaced nginx edge with a Caddy service that mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no longer publishes a port directly — Caddy is the only public ingress. - Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when HTTPS_MODE=skip so a fresh host without mkcert can still boot. - docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with WebSocket upgrade preserved and an HTTP→HTTPS redirect. - start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so the legacy http://localhost:${APP_PORT} URL keeps working. In local/byo mode it prints the https://${LOCAL_DOMAIN} URL. - .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT, HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional. Tooling - scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert, mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN). Tests - artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass. - scripts/tests/local-setup.test.mjs: 2/2 pass. Constraints honored: no force-push, no destructive ops, start.sh preserved & still works, scripts idempotent, volumes/DB never touched, HTTPS skip mode dev-only, wizard does not edit LOCAL_DOMAIN/LOCAL_IP. Out of scope / not addressed: pre-existing TS errors in routes/users.ts and pre-existing failure in executive-meetings-postpone-race.test.mjs. |