62c38a508f3554f18450af94533eb8ad697c0bf2
796 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2f591a6ba8 |
Show the notifications app on the main page again
Remove hardcoded exclusion for the notifications app in the home page component. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 58af0916-ab79-457c-868e-3b72336f6067 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1WKAcHk Replit-Helium-Checkpoint-Created: true |
||
|
|
b5efd9eb12 |
Add system updates page to admin section
Add a new API endpoint and UI section for checking system updates, including internationalized locale strings and necessary dependency updates. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 1e6de894-7c78-4557-b01a-a2c1c01f4155 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/1WKAcHk Replit-Helium-Checkpoint-Created: true |
||
|
|
191e072353 |
#571: ship Calendar/Notifications disabled by default + drop Notifications tile from home
Task: on fresh installs the Calendar and Notifications apps were appearing enabled by default, and the home screen showed a Notifications tile that was redundant with the bell icon already in the top bar. User wanted both apps disabled by default and the home tile gone (bell stays). Changes: - scripts/src/seed.ts: flip the seed `isActive` for `notifications` and `calendar` from true → false. The seed inserts apps with `db.insert(appsTable).values(apps).onConflictDoNothing()`, so this is fully idempotent: existing environments (where the row already exists with isActive=true) are NOT changed; only fresh inserts on new installs pick up the disabled default. Admins can enable either app from app settings when they want it. - artifacts/tx-os/src/pages/home.tsx: remove "notifications" from the `dockApps` filter so the bottom dock no longer renders a Notifications tile. Filter becomes `["services", "admin"]`. The top-bar bell icon (and its unread badge) was untouched and still routes to `/notifications`. Out of scope (per spec): no migration to disable existing prod rows; no role/permission changes; bell icon in the top bar stays. Files: - scripts/src/seed.ts (notifications block ~L229, calendar block ~L275) - artifacts/tx-os/src/pages/home.tsx:532 |
||
|
|
2aa4a12ddc |
#571: ship Calendar/Notifications disabled by default + drop Notifications tile from home
Task: on fresh installs the Calendar and Notifications apps were appearing enabled by default, and the home screen showed a Notifications tile that was redundant with the bell icon already in the top bar. User wanted both apps disabled by default and the home tile gone (bell stays). Changes: - scripts/src/seed.ts: flip the seed `isActive` for `notifications` and `calendar` from true → false. The seed inserts apps with `db.insert(appsTable).values(apps).onConflictDoNothing()`, so this is fully idempotent: existing environments (where the row already exists with isActive=true) are NOT changed; only fresh inserts on new installs pick up the disabled default. Admins can enable either app from app settings when they want it. - artifacts/tx-os/src/pages/home.tsx: remove "notifications" from the `dockApps` filter so the bottom dock no longer renders a Notifications tile. Filter becomes `["services", "admin"]`. The top-bar bell icon (and its unread badge) was untouched and still routes to `/notifications`. Out of scope (per spec): no migration to disable existing prod rows; no role/permission changes; bell icon in the top bar stays. Files: - scripts/src/seed.ts (notifications block ~L229, calendar block ~L275) - artifacts/tx-os/src/pages/home.tsx:532 |
||
|
|
c40384b0d9 |
#570: stronger selected-row highlight + Edit button in Quick Actions
Task: on iPad the bulk-selected row was nearly invisible (only a 2px inset navy ring), and the row-tap Quick Actions dialog had only a Postpone button — user asked for a real visible selection fill and an Edit (pencil) action wired to the existing edit-meeting flow. Changes (artifacts/tx-os/src/pages/executive-meetings.tsx): - MeetingRow: add `selectionBg = rgba(11,30,63,0.18)` painted on the row's `<tr>` backgroundColor in addition to the existing `selectionShadow` 2px navy ring. Composition order in trStyle is `quickOpenBg ?? selectionBg ?? baseBg` so an open quick-actions surface still wins (transient action state), user-picked row colours still show when nothing is selected, and the navy tint reads clearly on iPad against the table grid. - MeetingRow: new optional `onQuickEdit?: () => void` prop. Quick Actions Dialog gains a pencil-icon "Edit" button before the existing Postpone button, wrapped in `flex flex-wrap gap-2` so the two buttons sit side-by-side on iPad/desktop and stack on narrow viewports. Button only renders when a handler is wired (defensive — `quickActionsCanMutate` already gates the surface). - ScheduleSection: host a local `editingMeeting` + `editingSaving` state plus a `MeetingFormDialog` mount, with a save handler whose body mirrors ManageSection's `save()` exactly (same PATCH payload projection, same `wrapAsParagraph` / attendee shaping, same toast/error translation). Edit button on a row opens this in-place dialog instead of switching the user to the Manage tab. - Locale: add `executiveMeetings.quickActions.edit` = "تعديل" / "Edit". Notes: - No new icon import needed — `Pencil` was already in the lucide import group. - Selection ring (`#0B1E3F`) and tint use the same navy so they read as one cohesive selected state, not competing layers. - Pre-existing `use-push-subscription.ts` TS noise unchanged. |
||
|
|
dc30d75e11 |
Hide redundant clock and navigation elements for president view
Modify executive-meetings.tsx to conditionally hide the HeaderClock and section navigation when isPresidentView is true. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: e1c6509c-638c-4c43-8d4f-4b4a5910fc4a 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 |
||
|
|
21dda3d372 |
#572 president-only focused view for executive meetings
User asked that when the President (`executive_ceo` role) logs into
the Meetings page he sees a clean "presentation" layout: just the
schedule table, an analog clock plus the weekday/date of the
schedule, and nothing else. The Manage / Notifications / Audit /
PDF tabs and the PDF-export shortcut button are noisy for him and
must disappear — but his backend permissions stay intact.
Changes (frontend only — backend role gates untouched):
- New component `components/executive-meetings/analog-clock.tsx`:
small SVG analog clock (hour/minute/second hands, navy + gold),
ticks every second, ~44px default.
- New inner `PresidentClockBar` component in `executive-meetings.tsx`:
renders the analog clock + weekday + **digital time** (ticking
per second, localized) + selected schedule date in one block.
- `pages/executive-meetings.tsx`:
- Derived `isPresidentView` from the `/me` roles: true iff
`executive_ceo` is present AND the user is NOT also `admin`
or `executive_office_manager` (admins keep full UI).
- Effect forces `section = "schedule"` whenever
`isPresidentView` becomes true, so the president can never
land on a now-hidden tab.
- Hides the header PDF-shortcut button and the entire SECTIONS
tab list when `isPresidentView`. The `#em-toolbar-portal` is
kept so the date picker still mounts.
- `ScheduleSection` accepts two new optional props:
`isPresidentView` and `lang`. When `isPresidentView` is on,
the toolbar renders the AnalogClock next to a label showing
`formatWeekday(date, lang, "long")` and `formatDate(date, lang)`
(e.g. "Sunday · May 17, 2026" / "الأحد · 17 مايو 2026"). The
label updates immediately when the date input changes.
- Edit-mode toggle stays gated on `canMutate`, so the president
(who has canMutate) still gets the inline-edit pencil if he
wants it.
Out of scope: backend role changes, applying the focused view to
other roles, manual presentation-mode toggle. Verified via tsc
(only the pre-existing `use-push-subscription` error remains).
|
||
|
|
27384d019a |
#572 president-only focused view for executive meetings
User asked that when the President (`executive_ceo` role) logs into
the Meetings page he sees a clean "presentation" layout: just the
schedule table, an analog clock plus the weekday/date of the
schedule, and nothing else. The Manage / Notifications / Audit /
PDF tabs and the PDF-export shortcut button are noisy for him and
must disappear — but his backend permissions stay intact.
Changes (frontend only — backend role gates untouched):
- New component `components/executive-meetings/analog-clock.tsx`:
small SVG analog clock (hour/minute/second hands, navy + gold),
ticks every second, ~44px default.
- `pages/executive-meetings.tsx`:
- Derived `isPresidentView` from the `/me` roles: true iff
`executive_ceo` is present AND the user is NOT also `admin`
or `executive_office_manager` (admins keep full UI).
- Effect forces `section = "schedule"` whenever
`isPresidentView` becomes true, so the president can never
land on a now-hidden tab.
- Hides the header PDF-shortcut button and the entire SECTIONS
tab list when `isPresidentView`. The `#em-toolbar-portal` is
kept so the date picker still mounts.
- `ScheduleSection` accepts two new optional props:
`isPresidentView` and `lang`. When `isPresidentView` is on,
the toolbar renders the AnalogClock next to a label showing
`formatWeekday(date, lang, "long")` and `formatDate(date, lang)`
(e.g. "Sunday · May 17, 2026" / "الأحد · 17 مايو 2026"). The
label updates immediately when the date input changes.
- Edit-mode toggle stays gated on `canMutate`, so the president
(who has canMutate) still gets the inline-edit pencil if he
wants it.
Out of scope: backend role changes, applying the focused view to
other roles, manual presentation-mode toggle. Verified via tsc
(only the pre-existing `use-push-subscription` error remains).
|
||
|
|
4b151c50a8 |
#568 silent delete for finished orders
User requested that deleting a finished order from My Orders happen
silently — one click on the trash icon, no confirmation dialog, no
success toast. Just click and the card disappears.
Changes:
- Removed the delete-confirm AlertDialog from OrderCard along with
its `confirmDeleteOpen` state. The trash icon now calls
`handleDelete` directly.
- Removed `toast({ title: t("myOrders.deleted") })` from
`scheduleDelete`. The 7-second soft-delete grace window stays in
place (pendingDeleteIds filter + setTimeout) so the API DELETE
still fires after the window. The `deleteFailed` error toast is
preserved so silent API failures don't lose orders.
- Dropped the now-unused locale keys from ar.json and en.json:
`deleteConfirmTitle`, `deleteConfirmBody`, `deleteConfirm`,
`deleted`.
- Removed the unused `AlertDialogDescription` import (the remaining
cancel-confirm dialog uses only the title).
Cancel-order flow remains untouched (still shows confirm dialog and
undo toast as in #566).
|
||
|
|
4c57c58f8c |
#566 clean up My Orders page (3 removals)
Per user request on the طلباتي page: 1. Removed the "Clear finished orders" bulk button entirely. The button was showing as a raw i18n key (myOrder c...) for the user and they asked for it gone. Dropped the related AlertDialog, confirmClearOpen state, handleClearFinished handler, and the locale keys clearFinished_*, clearConfirmTitle/Body_*, clearConfirm, clearedCount_*, and clearedPartial in both ar.json and en.json. 2. Removed the "تراجع / Undo" ToastAction shown after deleting an order. The toast now displays only the "Order deleted" title; the 7-second timer + pendingDeleteIds soft-delete window is preserved so the actual DELETE still fires after the grace period. The undo closure was deleted since nothing references it anymore. Single-id call site stays untouched (scheduleDelete still receives [id]). 3. Removed the "ستتاح لك ثوانٍ قليلة للتراجع" description from the cancel-order confirmation dialog. The cancelConfirmBody key was dropped from both locales. The AlertDialog now shows only the title + the two action buttons. Out of scope (kept as-is): - The undo ToastAction after CANCEL (user only asked about delete). - Incoming-orders page. Notes: - ToastAction import is still used by the cancel-undo path. - Trash2 import still used by the per-card delete button. - Pre-existing TS error in use-push-subscription.ts is unrelated. |
||
|
|
4e5bed602e |
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). |
||
|
|
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). |
||
|
|
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
(
|
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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). |
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
e1ec082e50 |
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. |
||
|
|
78d5c91328 |
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. |
||
|
|
d4412329c7 |
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. |
||
|
|
182d8f96dd |
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. |
||
|
|
04933be2df |
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. |
||
|
|
74e6d5478b |
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. |
||
|
|
a3ebff2afa |
feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, and tooling only. UI ships in Stage 2. 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. - Zod validation, bcrypt hashing, in-memory rate limiter for 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. The web service no longer publishes a port directly — Caddy is the only public ingress. - docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with WebSocket upgrade preserved and a plaintext :80 fallback when HTTPS_MODE=skip (dev-only). - .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). start.sh untouched. Tests - artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass (snapshot/restore admin role + system_settings around tests). - scripts/tests/local-setup.test.mjs: 2/2 pass (first-run bootstrap + second-run no-op idempotency with mkcert/openssl stubs). Constraints honored: no force-push, no destructive ops, start.sh preserved, 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. |
||
|
|
40b5f7773c |
Update website's default sharing image
Update opengraph.jpg in the public artifacts directory. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 6a7af115-92ba-48dc-a8b6-068ee87c1c7d 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 |
||
|
|
7a2ae8434d |
Update project documentation and code comments to remove platform-specific references
Refactor documentation files and code comments to remove references to Replit, specific task numbers, and other platform-specific identifiers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fa18e5d4-a810-4bd5-8cde-2a60d64d9e3f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu Replit-Helium-Checkpoint-Created: true |
||
|
|
3af737186d |
Improve admin modal design and accessibility with shared component
Refactors AdminFormDialog to create a reusable component for admin modals, including aria-labelledby/describedby attributes and Escape key closing functionality. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fa532550-4873-409b-840a-ac4c474132f3 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu Replit-Helium-Checkpoint-Created: true |
||
|
|
a15365f459 |
Task #531: Redesign DeletionWarningDialog for a more professional look
Scope: artifacts/tx-os/src/pages/admin.tsx — the shared confirm
dialog used for deleting Apps, Services, and Users on the admin
page.
What changed
- Three explicit visual regions instead of a flat stack:
- Header: AlertTriangle icon in a destructive-tinted circle next
to the bold title (which already contains the item name from
the existing translation strings).
- Body: warning copy, then impacted items rendered inside a
sub-card (rounded border + subtle bg + custom bullet dots
instead of `list-disc`), then a destructive-tinted callout
holding the "cannot be undone" hint.
- Footer: separated by a top border and a faint bg tint;
Cancel = ghost (quieter), Confirm/Anyway = destructive (clear
primary action). When `isPending`, a small spinner appears
inside the destructive button.
- Slightly larger max-width (max-w-md), shadow-xl, and overflow-
hidden so the rounded corners read cleanly with the new footer
divider.
- `role="alertdialog"` + `aria-modal="true"` added on the panel.
Behaviour preserved
- Component props, call sites, and all `data-testid` hooks are
unchanged (testId, confirmTestId — covers app-delete-dialog/
-confirm, service-delete-dialog/-confirm, user-delete-dialog/
-confirm).
- No translation keys added; reuses every existing
admin.deleteApp/Service/User.* string.
- Cancel renders before Confirm in the DOM so tab order is
Cancel -> Destructive (no a11y regression). Visual order is
controlled with `justify-end`, which mirrors correctly in RTL.
- Pure UI change: no API/handler/permission logic touched.
Notes
- Architect flagged an earlier `flex-row-reverse` footer for
putting the destructive button first in tab order; fixed before
marking complete.
- Pre-existing TS18046 / TS2345 errors in admin.tsx are unrelated.
|
||
|
|
21ccb6c426 |
Task #531: Redesign DeletionWarningDialog for a more professional look
Scope: artifacts/tx-os/src/pages/admin.tsx — the shared confirm
dialog used for deleting Apps, Services, and Users on the admin
page.
What changed
- Three explicit visual regions instead of a flat stack:
- Header: AlertTriangle icon in a destructive-tinted circle next
to the bold title (which already contains the item name from
the existing translation strings).
- Body: warning copy, then impacted items rendered inside a
sub-card (rounded border + subtle bg + custom bullet dots
instead of `list-disc`), then a destructive-tinted callout
holding the "cannot be undone" hint.
- Footer: separated by a top border and a faint bg tint;
Cancel = ghost (quieter), Confirm/Anyway = destructive (clear
primary action). When `isPending`, a small spinner appears
inside the destructive button.
- Slightly larger max-width (max-w-md), shadow-xl, and overflow-
hidden so the rounded corners read cleanly with the new footer
divider.
- `role="alertdialog"` + `aria-modal="true"` added on the panel.
Behaviour preserved
- Component props, call sites, and all `data-testid` hooks are
unchanged (testId, confirmTestId — covers app-delete-dialog/
-confirm, service-delete-dialog/-confirm, user-delete-dialog/
-confirm).
- No translation keys added; reuses every existing
admin.deleteApp/Service/User.* string.
- Cancel renders before Confirm in the DOM so tab order is
Cancel -> Destructive (no a11y regression). Visual order is
controlled with `justify-end`, which mirrors correctly in RTL.
- Pure UI change: no API/handler/permission logic touched.
Notes
- Architect flagged an earlier `flex-row-reverse` footer for
putting the destructive button first in tab order; fixed before
marking complete.
- Pre-existing TS18046 / TS2345 errors in admin.tsx are unrelated.
|
||
|
|
60fbd3dd84 |
Task #529: Restore topbar logout + collapsible Settings groups
home.tsx - Re-import LogOut from lucide-react. - Append a single-tap Logout icon button after <SettingsPanel /> in the right-side cluster, wired to the existing handleLogout flow. - Drop the onLogout prop from <SettingsPanel />; update the topbar comment block to describe the Bell -> Inbox -> Settings -> Logout cluster. settings-panel.tsx - Drop the LogOut import, the onLogout prop on SettingsPanel and SettingsPanelBody, the local handleLogout wrapper, and the inline logout button (data-testid="settings-logout"). - Wrap the four groups (language, notifications, sound, clock) in the existing shadcn Accordion (type="multiple") so each toggles independently. Default = all collapsed; open state is lifted to SettingsPanel via useState<GroupId[]> so toggles persist while the panel is closed and re-opened in the same session. - New GroupItem helper styles each AccordionItem like the prior GroupCard so the panel keeps its rounded-card visual rhythm. The trigger uses text-start so headers align correctly in RTL. - Existing form bodies (LanguageBody, NotificationSettingsContent, SoundBody, ClockStyleContent) are reused unchanged, preserving all DB + localStorage persistence. Notes - Logout placement and removal from the panel were both explicit user requests (clarified in plan #529 after #527 unified the topbar). - No new i18n keys; existing settingsPanel.section.* labels reused as accordion triggers. - typecheck clean (pre-existing TS6305/TS7006 noise unrelated). |
||
|
|
7abb0c870b |
Task #527: unify topbar into per-user Settings panel
Topbar right cluster collapses from 5–7 icons to exactly Bell -> Inbox
(conditional on canReceiveOrders) -> Settings (gear). Logout moved
inside the Settings panel; the second bell, globe, volume and clock
buttons are gone from the topbar.
New SettingsPanel renders as a Popover on md+ and a bottom Sheet on
<md (via new useMediaQuery hook). It composes existing controls
rather than re-implementing them:
- Language radio uses useUpdateLanguage + i18n.changeLanguage
- Notifications group renders extracted NotificationSettingsContent
- Sound group toggles notificationsMuted (same flag QuickMute used)
- Clock group renders extracted ClockStyleContent
All DB + localStorage persistence is preserved unchanged.
Refactors:
- notification-settings.tsx: extracted NotificationSettingsContent;
NotificationSettingsPicker now wraps it (kept for any future caller).
- clock-style-picker.tsx: extracted ClockStyleContent; ClockStylePicker
now wraps it; removed dead setOpen reference inside choose().
- home.tsx: removed Globe / QuickMute / NotificationSettingsPicker /
ClockStylePicker / standalone LogOut from the cluster, dropped
related imports + useUpdateLanguage, reordered to Bell -> Inbox ->
Settings, and passes onLogout to SettingsPanel.
i18n: added settingsPanel.{title, section.*, lang.*, sound.master}
keys to ar.json and en.json.
Code review: PASS.
|
||
|
|
e6a3b148f4 |
Task #527: unify topbar into per-user Settings panel
Topbar right cluster collapses from 5–7 icons to exactly Bell -> Inbox
(conditional on canReceiveOrders) -> Settings (gear). Logout moved
inside the Settings panel; the second bell, globe, volume and clock
buttons are gone from the topbar.
New SettingsPanel renders as a Popover on md+ and a bottom Sheet on
<md (via new useMediaQuery hook). It composes existing controls
rather than re-implementing them:
- Language radio uses useUpdateLanguage + i18n.changeLanguage
- Notifications group renders extracted NotificationSettingsContent
- Sound group toggles notificationsMuted (same flag QuickMute used)
- Clock group renders extracted ClockStyleContent
All DB + localStorage persistence is preserved unchanged.
Refactors:
- notification-settings.tsx: extracted NotificationSettingsContent;
NotificationSettingsPicker now wraps it (kept for any future caller).
- clock-style-picker.tsx: extracted ClockStyleContent; ClockStylePicker
now wraps it; removed dead setOpen reference inside choose().
- home.tsx: removed Globe / QuickMute / NotificationSettingsPicker /
ClockStylePicker / standalone LogOut from the cluster, dropped
related imports + useUpdateLanguage, reordered to Bell -> Inbox ->
Settings, and passes onLogout to SettingsPanel.
i18n: added settingsPanel.{title, section.*, lang.*, sound.master}
keys to ar.json and en.json.
Code review: PASS.
|
||
|
|
04f2ab7ea9 |
Task #527: Unify topbar prefs into per-user Settings panel
- New `settings-panel.tsx` consolidates four duplicate topbar buttons
(clock-style picker, quick-mute, notification-settings popover,
language-toggle globe) behind a single gear icon that opens a
right-side Sheet (full-width on mobile, capped on desktop).
- Panel groups: Language (ar/en), Notifications (per-slot
enabled/sound/vibration), Sound (master mute), Clock (visibility,
12/24h, style).
- Reuses existing hooks (useUpdateLanguage, useUpdate{Clock*},
useUpdatePrefs, useHome/TopbarClockVisibility) so all persistence
paths (DB columns + localStorage) stay identical — no migration.
- home.tsx topbar now renders only inbox link, single notification
bell (kept as nav link), Settings gear, and logout.
- New i18n keys under `settingsPanel.*` in ar.json + en.json.
- Old ClockStylePicker / NotificationSettingsPicker / QuickMuteButton
exports remain in their original files (unused) — kept to minimize
diff scope; can be removed in a follow-up.
|
||
|
|
2fa608b93d |
Add migration script for database updates and seeding
Adds a new `migrate` script to the API server package.json and updates the docker-compose.yml to use this script for database migrations and seeding. Also updates the README.md to reflect the new migration command. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 5a3829b7-335a-40a6-98a8-de6f98c908ff Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/jLdqQ2v Replit-Helium-Checkpoint-Created: true |