Original task (#91): The `app_permissions` join table had no unique
constraint, allowing the same `(app_id, permission_id)` pair to be
inserted repeatedly. The Admin Panel restriction had grown to 24
duplicate rows. This change prevents that recurring at the DB level
and verifies that the existing conflict-safe insert path keeps working.
Schema change:
- `lib/db/src/schema/apps.ts`: added a composite primary key on
`(app_id, permission_id)` for `appPermissionsTable`, matching the
pattern used by other join tables in this repo (rolePermissions,
userRoles, groupApps, etc.). This is enforced at the database level.
DB migration:
- Cleaned up the 23 duplicate rows still present in the DB before
applying the constraint (kept the earliest row per pair using
`ctid`), then ran `pnpm --filter @workspace/db run push` to sync the
schema. Verified the new primary key
`app_permissions_app_id_permission_id_pk` rejects duplicate inserts.
Graceful handling of duplicates:
- The seed script (`scripts/src/seed.ts`) already uses
`.onConflictDoNothing()` when inserting into `app_permissions`. With
the new primary key, that call is now properly idempotent (no longer
silently allowing duplicates).
- There is currently no HTTP route or admin UI that POSTs into
`app_permissions` (the task description listed `routes/apps.ts` as a
relevant file, but no such handler exists today). The constraint
itself is what prevents future regressions, and any future endpoint
should follow the seed's `.onConflictDoNothing()` pattern.
Tests:
- Added `artifacts/api-server/tests/app-permissions-unique.test.mjs`
with two cases that prove the new behavior:
1. A plain duplicate INSERT fails with SQLSTATE 23505 (unique
violation) and only one row remains.
2. `INSERT ... ON CONFLICT DO NOTHING` (the pattern Drizzle's
`.onConflictDoNothing()` emits) handles duplicates gracefully:
no error thrown, `rowCount` is 0, and exactly one row remains.
- All previously-passing api-server tests for apps/groups still pass
after the schema change.
Replit-Task-Id: 0589a4dc-5898-4c66-8feb-3cd48289fe89
Original task (#108): polish the daily-schedule UI and ship the
remaining 8 sections of the Executive Meetings module (Manage,
Requests, Approvals, Tasks, Notifications, Audit, PDF, Font Settings)
with bilingual i18n and fine-grained RBAC.
What changed:
- Schedule UI: centered cells, '#' header (not 'م'), attendees widest,
RTL column order locked, navy/white/gray + red highlighting only.
- Schema: made executive_meeting_requests.meetingId nullable so
'create-meeting' suggestions don't need a target row.
- Backend rewrite of artifacts/api-server/src/routes/executive-meetings.ts:
GET /me (now returns userId), full CRUD for meetings (transactional
attendee replace), requests CRUD + approve/reject/withdraw, tasks
CRUD with assignee status updates, audit-logs GET (admin), notifications
GET, font-settings GET/PATCH (per-user + global). RBAC via 5 role
sets and a makeRequireRoles middleware factory.
- Audit-in-transaction: every mutation (meeting/request/task/font CRUD)
wraps the DB write AND the audit-log insert in the same db.transaction
so audit entries cannot drift from state if either insert fails. This
was the main finding from the architect review and is now fixed.
- Path-to-regexp 8 fix: replaced inline ':id(\\d+)' with router.param('id')
+ next('route') guard, plus NaN guards in handlers.
- Frontend rewrite of artifacts/tx-os/src/pages/executive-meetings.tsx
(~2200 lines): 8 new section components, RBAC-aware UI via /me,
per-task assignee check now shown for status buttons.
- i18n: full executiveMeetings.* key set added in ar.json + en.json.
Verified: full e2e (admin login -> all 9 tabs -> create meeting ->
submit request -> approve -> audit shows full chain -> font save)
plus a smoke regression after the audit-in-tx refactor (atomic
audit row created in lockstep with mutation).
Out of scope (proposed as follow-ups #110-#112): real notification
delivery, real PDF generation (currently window.print), and
automated integration tests for the new endpoints. The pre-existing
'apps-open.test.mjs' syntax error in the test workflow is unrelated
to this task.
What changed:
- Added an `is_system` column to the `roles` table (default 0). The
built-in roles (admin, user, order_receiver) are flagged as system roles
in the seed and via a one-off UPDATE on the existing dev DB.
- Created a new `/api/roles` route module with admin-guarded endpoints:
- POST /api/roles – create a role (validates name format, rejects duplicates).
- PATCH /api/roles/:id – update bilingual descriptions; name is immutable.
- DELETE /api/roles/:id – returns 400 for system roles (also defended
by name allowlist), 409 with userCount/groupCount when in use, 204 on
success. Cleans up role_permissions in a transaction.
GET /api/roles was moved out of groups.ts into the new module.
- OpenAPI updated with createRole / updateRole / deleteRole operations,
isSystem on the Role schema, and CreateRoleBody / UpdateRoleBody /
RoleDeletionConflict schemas. Regenerated api-zod and api-client-react.
- Added a "Roles" item under the User Management nav group in the admin
dashboard, plus a new RolesPanel with search, create dialog, edit dialog
(description-only), and delete dialog with conflict messaging.
- Bilingual translations added in en.json and ar.json (admin.nav.roles +
admin.roles.*).
Notes / deviations:
- Also marked `order_receiver` as a system role since it is seeded by the
system and required by the orders feature, even though the task brief
only called out admin and user.
- Verified end-to-end via the testing skill: list, create, edit, delete,
duplicate-name validation, and protection of system roles all work.
Replit-Task-Id: b1187555-be09-4687-a9ae-83b123d908bd
Original task: Task #79 — Make DELETE /api/groups/:id refuse to wipe a non-empty
group unless explicitly forced, surface the impacted counts in the admin UI,
and log forced deletions to an audit trail.
Changes:
- API: DELETE /api/groups/:id now computes member/app/role counts. If any are
non-zero and `?force=true` is not passed, it returns 409 with
{ error, memberCount, appCount, roleCount }. With `force=true` it deletes
and writes an entry to the new `audit_logs` table
(action='group.force_delete', target_type='group', target_id, metadata).
System group guard and 404 behaviour preserved.
- DB: Added `audit_logs` table (lib/db/src/schema/audit-logs.ts) with actor,
action, target type/id, jsonb metadata, timestamp. Pushed via drizzle-kit.
- OpenAPI: Added `force` query param, 409 response, and `GroupDeletionConflict`
schema. Re-ran orval codegen for api-client-react and api-zod.
- api-client-react: Re-exported `ApiError` and `ErrorType` so the UI can
inspect 409 responses.
- Admin UI (artifacts/teaboy-os/src/pages/admin.tsx GroupsPanel):
Replaced the bare `confirm()` with a confirmation dialog that shows the
group name and impacted member/app/role counts. Empty groups get a simple
"Delete" confirmation; non-empty groups get a warning + "Delete anyway"
button which sends `force=true`. If the server returns 409 (race), the
dialog updates to show the server-reported counts.
- Locales: Added en/ar strings for the new dialog (deleteTitle, deleteWarning,
deleteForceHint, deleteEmptyBody, deleteConfirm, deleteAnyway).
Verified end-to-end: 409 on first DELETE, dialog warning visible, force
deletion succeeds, single audit_logs row recorded with correct metadata.
Replit-Task-Id: 61624ce4-83b3-43be-b22b-e261662301f1
Replaces all user-facing instances of "TeaBoy" with "Tx" across the application, including titles, locale files, database settings, seed data, and API documentation. Also updates internal storage keys and session secrets to remove the old branding.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 07b19cb0-11b5-4be9-8932-ae4820eb73b8
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/HxTkDPZ
Replit-Helium-Checkpoint-Created: true
Backend:
- New schema: groups, user_groups, group_apps, group_roles (lib/db/src/schema/groups.ts)
- Seeds Admins, TeaBoy, Everyone system groups idempotently and maps existing users
- /api/groups CRUD with admin guard, batch counts, system-group delete protection
- Validates appIds/roleIds/userIds (400 on missing) and wraps assignment writes in
a single DB transaction (no partial state on failure)
- /api/users gains q/groupId/status filters, batch role+group loading, groupIds
replacement on PATCH, auto-assigns Everyone on admin-create
- /auth/register also auto-assigns Everyone for consistent default linkage
- buildAuthUser now returns groups (matches updated AuthUser OpenAPI schema)
- App visibility (getVisibleAppsForUser) unions group-granted apps via
group_apps + user_groups in addition to existing permission gating
Frontend (admin.tsx):
- Nav restructured: User Management section with Users + Groups children
- Section deep-linked via #section=… URL hash
- Users page rebuilt: search, group filter, status filter, sortable table,
groups column, edit-groups dialog, mobile cards
- New Groups page: cards with member/app/role counts, create dialog,
detail editor with Info/Apps/Users tabs and system-group guard
- ar/en translations added for all new keys
Testing:
- pnpm typecheck clean (api + web)
- 25/26 api tests pass; the only failure is pre-existing flaky pagination test
(admin-app-opens-pagination) — left as-is per scratchpad note
- Code review feedback addressed (validation, transactions, register auto-assign)
- New `service_orders` table (status pending|claimed|delivered|received|cancelled)
- New `orders:receive` permission + `order_receiver` role; admins implicitly allowed
- Added `requirePermission(name)` and `userHasPermission(userId, name)` middleware helpers
- New routes:
- POST /api/orders place order (authenticated)
- GET /api/orders/my list current user's orders
- GET /api/orders/incoming receivers see pending+active visible orders
- PATCH /api/orders/:id/status claim (atomic), deliver, cancel
- PATCH /api/orders/:id/confirm-receipt requester confirms a delivered order
- Atomic claim via UPDATE ... WHERE status='pending' AND assigned_to IS NULL
(returns 409 already_claimed on race)
- Realtime: emits `notification_created` to receivers/requester,
`order_incoming_changed` to all receivers, `order_updated` to requester
- Service shape in order responses limited to id/nameAr/nameEn/imageUrl
(no description fields), per spec
- OpenAPI updated with new paths and schemas; codegen run
- Seed updated idempotently (permission, role, role_permissions)
- New tests in artifacts/api-server/tests/service-orders.test.mjs
(full lifecycle, atomic claim race, unauth rejection) — all 21 api tests pass
No deviations from the planned scope. Tasks #63 (client UI) and #64
(receiver page + admin role toggle) remain blocked-by #62 and are next.
Integrates a new Notes feature, including backend API routes for notes and labels, database schema updates, frontend UI components for creating, viewing, editing, and deleting notes, and internationalization support for notes in both English and Arabic.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4ab7f101-06a9-4dd7-94c8-617f1751327c
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fYGOSe0
Replit-Helium-Checkpoint-Created: true
Original task (#38): When admins rename a group or add/remove members,
post a system message into the chat thread so other members see who
changed what and when, with bilingual (AR/EN) text and real-time
delivery.
Changes
- lib/db/src/schema/conversations.ts: added `kind` (varchar default
"user") and `meta` (jsonb) columns on the `messages` table. Pushed
the new columns directly via ALTER TABLE since drizzle-kit push
prompted on unrelated rename ambiguities. Also healed pre-existing
schema drift on `users.clock_hour12`,
`conversations.avatar_url`, and
`conversation_participants.is_muted/is_archived` so the API could
start.
- lib/api-spec/openapi.yaml: extended MessageWithSender with `kind`
(enum: user | group_renamed | members_added | member_removed) and
optional `meta`. Re-ran codegen.
- artifacts/api-server/src/routes/conversations.ts: added
insertAndEmitSystemMessage + small user-display helpers; PATCH
conversation now emits a `group_renamed` system message when a
name actually changes; add-participants emits `members_added` with
the actor + added users; remove-participant emits `member_removed`
with actor + removed user. Each system message is broadcast over
Socket.IO via the existing `new_message` channel so all current
members receive it immediately.
- artifacts/teaboy-os/src/pages/chat.tsx: render messages with
`kind != "user"` as centered, muted pill bubbles (no avatar /
sender label) using a new renderSystemMessage helper that picks
the language-appropriate name out of meta. Conversation list
preview also uses it so the last activity reads sensibly when the
most recent message is a system message.
- artifacts/teaboy-os/src/locales/{en,ar}.json: added chat.system.*
strings (groupRenamed, membersAdded with plural variants,
memberRemoved, someone, listSeparator).
Verification
- Typecheck (libs + artifacts) passes.
- e2e via testing skill: registered fresh users, created a group,
renamed it, added a member, removed a member; all three centered
system messages appeared in the thread in order with the expected
copy.
Notes / deviations
- Used the actor's userId as senderId for system messages (kept
existing NOT NULL FK) instead of introducing a nullable sender,
which keeps the migration lightweight. This means system messages
count toward unread for non-actor members; flagged as a follow-up.
Replit-Task-Id: 6dfa2b99-fbac-4146-b59b-8c04a14c9e96
Adds per-user mute / archive / leave actions for chats.
Schema:
- `lib/db/src/schema/conversations.ts`: added `is_muted` and
`is_archived` boolean columns on `conversation_participants`.
- Columns applied directly via SQL (drizzle push wanted to make
unrelated app_opens decisions; force-applied is_muted/is_archived
with `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`).
API (`artifacts/api-server/src/routes/conversations.ts`):
- New endpoint `PATCH /conversations/:id/state` — current user
toggles their own `isMuted` / `isArchived`.
- New endpoint `POST /conversations/:id/leave` — removes the
caller from a group conversation; rejects DMs.
- `buildConversationDetails` now returns `isMuted` and
`isArchived` for the requesting user.
- `sendMessage` auto-clears `isArchived` for all participants so
archived chats reappear when a new message arrives.
OpenAPI (`lib/api-spec/openapi.yaml`):
- Added the two new operations and `UpdateConversationStateBody`.
- Added `isMuted` / `isArchived` to `ConversationWithDetails`.
- Re-ran codegen for `@workspace/api-zod` and
`@workspace/api-client-react`.
UI (`artifacts/teaboy-os/src/pages/chat.tsx`):
- Header gets a kebab "chat actions" button visible for both DMs
and groups when a conversation is open.
- Action sheet offers Mute/Unmute, Archive/Unarchive, and
(groups only) Leave with a confirmation dialog.
- Conversation list now has Active / Archived tabs and a
bell-off indicator + dimmed unread badge for muted chats.
- Bilingual strings added to en.json and ar.json.
Side fixes (unrelated pre-existing schema drift discovered while
testing): added missing `users.clock_hour12` and
`conversations.avatar_url` columns directly so login and the
conversations list work; the schema files already declared them.
Verified end-to-end with the testing tool: mute, archive,
unarchive, and leave-group flows all pass.
Replit-Task-Id: faec58bb-12c6-4f6f-9ddb-f3f9f6c033f4
Adds upload + display of a custom avatar for group conversations.
Changes:
- DB: Added `avatar_url text` (nullable) to `conversations` table.
Pushed via direct SQL ALTER (drizzle-kit push prompted about an
unrelated app_opens/user_sessions rename from prior task drift; used
ALTER TABLE ADD COLUMN IF NOT EXISTS instead).
- OpenAPI (`lib/api-spec/openapi.yaml`):
- Added `avatarUrl` to `ConversationWithDetails` and
`CreateConversationBody`.
- Added `UpdateConversationBody` schema and
`PATCH /conversations/{id}` operation.
- Regenerated `@workspace/api-zod` and `@workspace/api-client-react`.
- API (`artifacts/api-server/src/routes/conversations.ts`):
- Persist `avatarUrl` on create.
- New `PATCH /conversations/:id` (admin-only) to update `avatarUrl`.
- Web (`artifacts/teaboy-os/src/pages/chat.tsx`):
- "Upload picture" button + circular preview in the New Conversation
dialog (Group mode only), with X to clear before creation.
- Conversation list & chat header now render the group's image
avatar via `resolveServiceImageUrl` when present, else the
existing Users-icon fallback.
- Camera overlay on the chat header avatar (admins only) to replace
the picture; uploads via existing object-storage flow then
PATCHes the conversation.
- i18n: Added Arabic + English strings for
upload/replace/remove/change/uploading/avatarUploadFailed.
Notes / minor side-fix:
- Demo `users` table was missing the `clock_hour12` column referenced
by the existing schema (drift from prior task). Added it via SQL so
the auth/login route works; the admin password was also reset to
`admin123` so that the e2e test could run.
- Direct conversations are unchanged (no avatar UI, no avatar saved).
Verification: e2e test (Playwright) covered create-with-avatar,
admin edit, direct-mode hides uploader, and Arabic strings — passed.
Replit-Task-Id: 34e8a2d2-621a-42a9-88ba-89652c6094dc
Add a per-user 12/24-hour clock preference that complements the existing
clock-style preference and is honored by every clock variant on the home
screen.
Schema & API
- Added `clockHour12 boolean` (nullable) column to `users` table
(lib/db/src/schema/users.ts) and synced via direct `ALTER TABLE`
because `drizzle-kit push` was blocked by an unrelated interactive
rename prompt for the existing `app_opens` table.
- Extended OpenAPI `AuthUser` and `UserProfile` with `clockHour12`,
added `UpdateClockHour12Body` schema, and a new
`PATCH /auth/me/clock-hour12` endpoint. Regenerated zod + react-query
clients via `pnpm --filter @workspace/api-spec run codegen`.
- Implemented the new route handler in
`artifacts/api-server/src/routes/auth.ts`; `buildAuthUser` now
surfaces `clockHour12`.
Frontend
- `lib/i18n-format.ts` no longer hard-codes `hour12: false`; callers
may pass `hour12` in options. Default remains 24-hour to keep all
other timestamps unchanged (chat etc. left as-is — see follow-up).
- `components/clock.tsx` exports `resolveClockHour12` and threads a new
`hour12` prop through `Clock` and `AnalogClockWidget`. All five
variants (full/digital/digital-no-seconds/analog/minimal) plus the
large analog widget now honor the choice.
- `components/clock-style-picker.tsx` gained a 12-hour / 24-hour
segmented toggle that calls the new endpoint with optimistic cache
updates. The variant previews also reflect the active hour format.
- `pages/home.tsx` passes `user.clockHour12` to the header clock,
widget, and picker.
- Added `home.clockStyle.hourFormat.{label,h12,h24}` strings in EN and
AR. Arabic uses Latin digits and "ص/م" via Intl's localized
dayPeriod.
Verification
- `pnpm -w run typecheck` passes.
- E2E test: logged in, switched to 12-hour, verified AM/PM in header
and previews, reloaded to confirm persistence, switched back to
24-hour, reloaded again — all green.
Replit-Task-Id: 03ea8cbe-ace7-4d36-afc5-49ebc9706c67
Original task #20: Let each user pick their own home-screen clock
style (analog/digital/minimal/etc.), persisted on the user record
and restored on login / other devices. Default = "full".
Changes:
- DB: added `clock_style varchar(30)` (nullable) to `users`
(lib/db/src/schema/users.ts) and applied via direct ALTER TABLE
(drizzle-kit push had unrelated interactive prompts about
app_opens / user_sessions which were not safe to answer).
- OpenAPI: added `ClockStyle` enum (full/digital/digital-no-seconds
/analog/minimal), `UpdateClockStyleBody`, exposed `clockStyle`
on AuthUser and UserProfile, and added PATCH /auth/me/clock-style.
Regenerated typed client and zod schemas.
- API: `buildAuthUser` and `buildUserProfile` include `clockStyle`;
new authenticated endpoint updates the current user's style and
returns the refreshed AuthUser.
- Frontend: new `Clock` component (artifacts/teaboy-os/src/
components/clock.tsx) with five variants sharing a single `useNow`
tick hook, plus an SVG analog clock; honors existing
Latin-digit/locale formatting helpers. New `ClockStylePicker`
(popover) shown in the status bar with a live preview for each
option and optimistic update through the AuthUser query cache.
- home.tsx replaces the hard-coded clock block with `<Clock>` driven
by `user.clockStyle`; trigger button placed next to the language
toggle.
- i18n: added `home.clockStyle.label` and per-style option labels
to en.json and ar.json.
Verified via e2e: register → default "full" rendered → switch to
analog → reload → analog persists → switch to minimal → time-only
renders. RTL layout + Latin digits both correct after language
toggle.
Replit-Task-Id: 475a7439-b357-400d-805f-5f2fda20ed24
Task #18: self-service "Forgot password?" flow on the sign-in page,
plus an admin-mediated delivery path so tokens actually reach users
without email infrastructure.
Changes:
- New password_reset_tokens table (SHA-256 hashed token, 1h TTL,
single-use) added via schema + raw SQL.
- Public endpoints: POST /auth/forgot-password (identical response
for valid/invalid identifiers, no account enumeration),
POST /auth/reset-password/verify, POST /auth/reset-password.
Raw tokens are never returned or logged — only id + expiry.
- Admin-only endpoint: POST /auth/admin/users/:id/issue-reset-link
returns a one-time reset URL (origin + hex token) so admins can
share it with the user out-of-band until email delivery lands.
- Frontend: "Forgot password?" link on login, new /forgot-password
and /reset-password pages, admin Users list gets a KeyRound button
opening a modal with the generated URL and a Copy button. Public
routes /forgot-password and /reset-password registered in
AuthContext.
- Bilingual EN/AR copy for all new screens and admin modal.
Verification: full end-to-end test passed — admin generated link,
user reset password via link, logged in with new password, and
reused token was rejected as invalid (single-use enforced).
Follow-ups proposed: #25 transactional email delivery, #26 rate
limiting on the public reset endpoints.
Replit-Task-Id: e7628acb-8901-4b62-a7ee-a1149d9e993f
- New `app_opens` table (id, user_id, app_id, created_at) and Drizzle
schema; exported from lib/db schema index.
- New `POST /api/apps/{id}/open` endpoint logs an open for the current
user (auth required, 204 on success, 404 for unknown app id).
- Extended `GET /api/stats/admin` with appOpensByDay/appOpensLast7Days/
appOpensPrev7Days and servicesCreatedByDay/servicesCreatedLast7Days,
refactored around a shared buildSeries helper.
- Regenerated api-client/zod and added two new bar charts (App opens,
Services added) to the admin Dashboard alongside the existing
Sign-ups chart, with EN/AR translations.
- Home page fires bare `logAppOpen(id, { keepalive: true })` before
navigating so the request survives client-side route changes; the
bottom dock buttons also use this helper.
- Restructured SortableAppIcon so the click target and dnd-kit
listeners live on the same <button>, fixing a click-vs-drag
interaction that prevented the open event from firing in tests.
Rebase notes:
- home.tsx: incoming main reworked AppIcon styling, the apps grid
header (with count and empty state), and the dock button. Kept all
incoming visual changes while preserving this task's
AppIconContent fragment, single-button SortableAppIcon, and
openApp() wiring (so dock + grid both log opens).
- opengraph.jpg: kept incoming binary version.
E2E verified: clicking app icons increments app_opens; admin dashboard
renders all three charts.
Replit-Task-Id: 776c14f7-4e5a-4bf6-80e2-a6c7586c1fcb
Add editable footer text fields to site settings, update OpenAPI schema and API client, refactor the login page to display AI-generated artwork and use dynamic footer text, and remove the old logo and welcome heading.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a650b496-7c3c-427e-bfbe-77bc8b9b5dd2
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/gdgNDb6
Replit-Helium-Checkpoint-Created: true
Implements drag-and-drop functionality for reordering apps using @dnd-kit, adds a new `userAppOrdersTable` to the database schema to store user-specific app order preferences, and introduces a new API endpoint `/api/me/app-order` for updating these preferences.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: e782e35b-00f5-4b9b-8931-63051a25df80
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PrQkd7G
Replit-Helium-Checkpoint-Created: true
User wants the admin to be able to open or close public self-registration
from inside the app instead of removing the registration page entirely.
Changes:
- Added registration_open boolean column to app_settings (default true)
- Exposed registrationOpen in AppSettings + UpdateAppSettingsBody schemas
in openapi.yaml; regenerated client + zod
- Backend: POST /api/auth/register now returns 403 "Registration is
closed" when the flag is off (checks settings row before any other work)
- Admin Site Settings panel now has a switch to toggle public
registration on/off, with bilingual label + helper text
- Login page hides the "Create account" link when registration is closed
- Register page short-circuits to a friendly "registration closed"
card with a Back to Login button when the flag is off (and redirects
if the user lands there directly)
- Added bilingual locale keys: registrationOpen, registrationOpenHint,
registrationClosed
Verified: GET /api/settings returns the new field; POST /api/auth/register
returns 403 when closed and proceeds normally when open. Both typecheck
suites pass.
User asked to remove the hardcoded "TeaBoy" branding and let admins
change the system name. Also completes the in-progress service image
upload work via App Storage.
Changes:
- New app_settings table (single row id=1) with siteNameAr/siteNameEn
- New /settings endpoints: GET (public) + PATCH (admin)
- Atomic ensureSettingsRow via INSERT ... ON CONFLICT DO NOTHING
to avoid race conditions
- New SiteSettingsPanel tab in admin page (Arabic + English inputs)
- New useAppName hook reads settings, updates document.title, falls
back to defaults while loading
- Login + register pages now display the dynamic site name
- Service image upload (App Storage) wired via useUpload + presigned
GCS URL flow; admin component ServiceImageUploader
- Storage routes: /storage/uploads/request-url and /storage/objects/*
now require auth (closes previously-open endpoints flagged by review)
- Added AppSettings/UpdateAppSettingsBody + storage schemas to
openapi.yaml; regenerated client and zod
- Exposed UploadResponse from @workspace/object-storage-web; added
composite:true so it can be referenced by teaboy-os tsconfig
Validation: typechecks pass for api-server and teaboy-os; settings GET
returns row; upload URL endpoint returns 401 without auth.
Completed full-stack TeaBoy OS build:
Backend (artifacts/api-server):
- Session-based auth with express-session + connect-pg-simple (PostgreSQL sessions)
- RBAC middleware (requireAuth, requireAdmin) with role-based guards
- REST routes for: auth (login/logout/register/me/language), apps CRUD, services CRUD,
conversations + messages, notifications, users (admin), home stats
- Socket.IO mounted on same HTTP server at /api/socket.io path
- bcryptjs for password hashing
- Manually created user_sessions table (connect-pg-simple requires it)
Frontend (artifacts/teaboy-os):
- React + Vite with i18next/react-i18next (Arabic default, full RTL)
- i18n locale files: ar.json + en.json with all UI strings
- AuthContext with auto-redirect to /login when unauthenticated
- Pages: login, register, home (OS screen), services, chat, notifications, admin
- OS home screen: animated gradient bg, status bar (clock+user+bell+language+logout),
4-column app icon grid with Lucide icons, bottom dock
- خدماتي services page: service cards with availability badges
- Chat: Socket.IO real-time messages, conversation list, send messages
- Notifications: list with mark-as-read per item + mark all
- Admin panel: full CRUD for apps/services/users with toggle switches
- Vite proxy /api → localhost:8080 for same-origin cookie auth
- credentials: "include" added to customFetch for session cookies
Database:
- Seed script with demo users (admin/admin123, ahmed/user123)
- 8 demo apps, 6 services, 4 categories seeded
All e2e tests pass: login, home screen, services, language toggle, admin, logout