11aaaf2abe8cdfbeffc1d8df42d0e97f0701362e
29 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
11aaaf2abe |
Replace executive-meetings print-to-PDF with server-side PDF generator
The "Download PDF" button on the executive-meetings page now hits a real
backend endpoint that returns a true PDF (no more browser print dialog),
respects each user's font preferences (family, size, weight, alignment)
with proper Arabic RTL shaping, and archives every download.
The renderer maps each saved fontFamily ("system", "Cairo", "Tajawal",
"Noto Naskh Arabic", "Amiri") to a concrete pair of bundled font files
so the chosen family genuinely changes the embedded glyphs — Cairo and
Tajawal pick Noto Sans Arabic, the Naskh-style families and the system
default pick Noto Naskh Arabic, and Latin glyphs render in DejaVu Sans
across the board. Headers, body cells, and footer all flow through the
same script-aware font selection.
Backend (artifacts/api-server)
- New GET /api/executive-meetings/pdf?date=&lang= route in
src/routes/executive-meetings.ts that fetches the day's meetings +
attendees, renders a PDF, uploads it to object storage, writes an
executive_meeting_pdf_archives row (date, generated_by, byte_size,
storage_url), and streams the file back inline.
- New src/lib/pdf-renderer.ts using pdfkit + bidi-js with bundled
Noto Naskh Arabic and DejaVu Sans fonts in assets/fonts/.
- Added byte_size column on executive_meeting_pdf_archives (also in
lib/db schema) and rebuilt lib/db.
- Added ambient types for bidi-js; installed @swc/helpers to satisfy
fontkit at runtime.
- build.mjs now copies pdfkit's data/ folder (Helvetica.afm, etc.)
into dist/data so the bundled server can construct PDFDocument.
Frontend (artifacts/tx-os)
- PdfSection in src/pages/executive-meetings.tsx now renders a single
"Download PDF" button that fetches the endpoint, builds a Blob, and
downloads it. Removed the print/archive-creation buttons.
- Archive list shows a Download button for new /objects/... rows and a
read-only "Legacy snapshot" badge for older print: rows.
- Added byteSize on PdfArchive + size formatting; updated en/ar locales.
Tests
- New test "PDF GET /executive-meetings/pdf returns a real PDF and
archives it" in tests/executive-meetings.test.mjs covers: bad-date
400, unauthenticated 401, real %PDF body + content-type/disposition,
archive row with byteSize/generatedBy/filePath, empty-day handling,
and font-family mapping (Cairo embeds NotoSansArabic; Noto Naskh
Arabic embeds NotoNaskhArabic).
- All 23 executive-meetings tests pass.
Rebase
- Rebased onto main-repl/main (
|
||
|
|
539f11e25d |
Task #152: cell-merge + current-meeting tint on EM schedule
Adds two related features to the executive-meetings schedule: 1. Current-meeting tinting — meeting/attendees/time cells of the row matching the current time get a soft wash of the user's highlight color (~13% alpha), keeping their original text color and the existing colored ring. The # cell stays solid white/red. 2. Cell-merge overlay — editors can merge "meeting+attendees", "meeting+attendees+time", or the whole row into a single free-text cell. Stored in the DB so all viewers see the same overlay; the originals (titleAr/En, attendees, start/endTime) stay untouched and are restored on Unmerge. Implementation notes - DB: 3 nullable columns added to executive_meetings (merge_start_column, merge_end_column, merge_text). Applied via direct ALTER TABLE because pnpm --filter db run push is blocked by the pre-existing app_permissions duplicate (Task #148, surfaced as the new follow-up #156). Schema file kept in sync. - API: meetingPatchSchema accepts an optional `merge` field (`null | {start,end,text}`) with start<=end ordering and server-side sanitizeRichText on the text. Reuses the existing PATCH route. - Frontend: new MergeMenu component + canonical-order range resolution so merges still render when boundary columns are hidden, and gracefully degrade to unmerged rendering (without losing DB state) when column reordering makes the visible span non-contiguous. Unmerge stays available whenever a stored merge exists, even in degraded state, and existing mergeText is preserved on re-merge. - i18n: en + ar keys under executiveMeetings.merge.* Out of scope (deferred to follow-ups #154 / #155, per task brief) - API + e2e test coverage for the merge endpoint and UI flows - Realtime emission for executive-meetings mutations (no existing emit on this surface; covered by #155) Validation - TypeScript compiles cleanly across api-server + tx-os. - API tests: 108/110 pass — the 2 failures are the pre-existing app_permissions-unique cases (Task #148, follow-up #156), unrelated to this change. - Architect code review approved after addressing hidden-column fallback, non-contiguous reorder degradation, and Unmerge-availability edge cases. |
||
|
|
a14b589006 |
Audit role permission changes (Task #100)
Record an audit trail every time a role's permissions change and surface recent history inside the role edit dialog. Schema (lib/db): - New `role_permission_audit` table: id, roleId (FK roles), actorUserId (FK users, nullable on delete), previousPermissionIds int[], newPermissionIds int[], createdAt. Created via raw SQL because `drizzle push` currently fails on a pre-existing app_permissions duplicate (followup #148 tracks fixing this). API (artifacts/api-server): - PUT /api/roles/:id/permissions now wraps the permission delete/insert, the legacy audit_logs row, and the new role_permission_audit row in a *single* transaction so the permission set and its audit trail always commit (or roll back) together. Previous IDs are also read inside the transaction to avoid races. - A role_permission_audit row is written on EVERY PUT call (per task spec), including no-op saves; the GET handler computes addedPermissionIds/removedPermissionIds so the History UI can distinguish meaningful changes from no-op saves. - New GET /api/roles/:id/audit (admin-only, ?limit=1..50, default 10) returns recent entries newest-first with computed added/removedPermissionIds and joined actor info. - New tests in tests/role-permission-audit.test.mjs cover write, every-call write (incl. no-op), GET ordering+diff+actor, no-op diff reporting, 404, and limit. Drive-by fixes: - Fixed pre-existing syntax corruption in tests/apps-open.test.mjs (stray `ccaPassa,` line from a prior bad merge that prevented the whole api-server test workflow from running). - Made tests/roles-crud.test.mjs "rejects an invalid name" robust to parallel test execution by scoping the leak check to the bad name instead of relying on a global row count. API spec / codegen (lib/api-spec, lib/api-client-react): - Added `getRolePermissionAudit` operation and `RolePermissionAuditEntry` schema; ran orval codegen. Frontend (artifacts/tx-os): - New RolePermissionHistory component renders inside the role edit dialog (between permissions and Save/Cancel) using useGetRolePermissionAudit. Shows timestamp, actor, added/removed permission names (resolved via permissionsById), and total count. - Save invalidates the audit query so the new entry appears immediately on the next open. - Bilingual i18n strings (en + ar with full Arabic plural variants: zero/one/two/few/many/other) under admin.roles.history*. Verification: - tx-os typecheck passes. - All 5 new audit tests + 13 existing roles tests pass. - E2E (testing skill) verified the full flow: empty state on a fresh role, save adds a permission, reopened dialog shows the new entry with actor name in Arabic. Docs: - replit.md updated to list `role_permission_audit` in Database Tables. Follow-ups proposed: #146 paginate/filter history, #147 audit other admin permission changes, #148 fix drizzle push duplicate. Replit-Task-Id: 9b8886a2-6e76-4072-b345-a772421fa161 |
||
|
|
5114b207da |
Executive Meetings: inline editing, drag reorder, and current-meeting highlight
Implements Task #122 — four features on the Executive Meetings schedule: 1. Word-like inline editing of meeting title and attendee names with bold, italic, underline, color, and font controls (Tiptap-backed EditableCell). 2. Drag-to-reorder column headers (dnd-kit), persisted in the existing em-schedule-cols-v1 storage key. Old up/down buttons removed. 3. Drag-to-reorder rows within a day. New POST /api/executive-meetings/reorder endpoint swaps daily_number in a single transaction using a two-phase negative→final assignment that respects the (date, daily_number) UNIQUE constraint. Audit-logged and role-guarded. 4. Highlight the meeting whose start/end window is "now", refreshed every 60 s. Toggle and color picker live in the customize popover and persist to localStorage. Backend changes - Widened titleAr / titleEn / attendees.name to text (applied via direct ALTER TABLE because db:push --force is currently blocked by Task #126). - New artifacts/api-server/src/lib/sanitize.ts with an allowlist for inline tags + safe inline styles. Applied to POST, PATCH, PUT-attendees, duplicate, and reorder paths so rich text round-trips safely. - Code-review fix: duplicate handler now re-sanitizes titleAr/titleEn. - Code-review fix: print page no longer interpolates the unsanitized attendee.title into dangerouslySetInnerHTML. - 4 new tests cover sanitization stripping, reorder happy path, cross-day rejection, and permission denial. Pre-existing app_permissions failures are unrelated and tracked by Task #126. Frontend changes - artifacts/tx-os/src/components/editable-cell.tsx with FormattingToolbar. - ScheduleSection wires saveTitle, saveAttendeeName, reorder mutation (optimistic with revert on error), highlight tick, and HighlightPrefs. - New SortableHeader component for column drag. - AttendeesCell now passes the original attendee index to PUT writes so edits reach the right row even after grouping into virtual/internal/ external sections. - Print page renders sanitized HTML via dangerouslySetInnerHTML on names and titles (titles still rendered as plain text on the print page). - Translation keys added in ar.json and en.json. Drift - Sanitization for attendee.title was identified by the architect review as a defense-in-depth gap and proposed as Task #130 rather than fixing inline; the print-page XSS path that depended on it was fixed directly. |
||
|
|
bdc19d5012 |
Prevent duplicate app permission entries from being saved
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 |
||
|
|
b4d47e1012 |
Executive Meetings Phase 2: full module + atomic audit
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. |
||
|
|
66bc96f97f |
Add executive meeting management system with role-based access
Implement a new API endpoint and UI for managing executive meetings, including role-based access control and database schema changes for meeting data and attendees. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 079afca5-8c32-40ea-b7d1-8bf26358ff75 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/BRoHDEI Replit-Helium-Checkpoint-Created: true |
||
|
|
19a20cc5a4 |
Let admins create and edit roles from the dashboard (Task #82)
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
|
||
|
|
cd0a8313e0 |
Warn admins before deleting non-empty groups
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 |
||
|
|
d0e6912017 |
Rename project and remove unused services
Update project name from teaboy-os to tx-os and remove obsolete services. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 3154f23a-748a-4118-aa41-fc01b7b1f04d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PVuelRZ Replit-Helium-Checkpoint-Created: true |
||
|
|
1f23e65c0b |
Update system name and references from TeaBoy to Tx
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 |
||
|
|
f5273af19f |
Task #74: Add groups system + admin User Management UI
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) |
||
|
|
2602edaca0 |
Task #62: Service Orders backend foundation (corrected)
After prior code review rejection, refactored to match spec exactly: - Permission renamed orders:receive → orders.receive (dot form), seeded with order_receiver role - service_orders table: user_id, service_id, notes, status (pending/received/preparing/completed/cancelled with CHECK), assigned_to, created_at, updated_at - /confirm-receipt is now the receiver atomic claim (UPDATE WHERE pending+unassigned), 409 already_claimed on miss - /status accepts preparing|completed|cancelled with permission matrix: * preparing/completed: assigned receiver or admin * cancelled: owner (pending|received) OR admin (any non-cancelled status) - requirePermission middleware no longer auto-bypasses admin; admin gets the permission via explicit seed grant - Notifications use type='order', relatedType='order' - Realtime emits notification_created (per receiver) + order_incoming_changed (broadcast) + order_updated (owner) - OpenAPI Order schema rewritten (no quantity, no per-status timestamps), endpoint summaries updated, codegen run - Tests cover: client place+list, non-receiver 403, no-role 403, parallel claim race (200/409), status matrix, owner-cancel rules, admin-cancel-completed, descriptions excluded from service summary All 24 api-server tests pass. Ready for code review re-check. |
||
|
|
cdf5bf4d33 |
Service Orders — backend foundation (Task #62)
- 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. |
||
|
|
a864b84567 |
Add notes functionality to the application with CRUD operations and labeling
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 |
||
|
|
b910733adc |
Prevent database synchronization issues by ignoring specific tables
Configure Drizzle to exclude the `user_sessions` table from schema introspection. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 4e1b5887-b92a-4851-b4ff-a7278a110e95 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/YPEna1J Replit-Helium-Checkpoint-Created: true |
||
|
|
db9cf7b315 |
Send system messages for group rename / add / remove
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 |
||
|
|
7b77de107f |
Task #31: Let people leave, mute, or archive a group chat
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 |
||
|
|
55b39f7d6f |
Task #29: Give each group chat its own picture
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
|
||
|
|
bfdfffd8b5 |
Task #28: Let users pick a 12-hour (AM/PM) clock instead of 24-hour
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
|
||
|
|
284cb751ed |
Add per-user clock style preference for the home status bar.
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 |
||
|
|
b915c01df7 |
Add forgot-password flow with admin-mediated reset links
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 |
||
|
|
a5484283f4 |
Add app opens & services activity charts to admin dashboard
- 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
|
||
|
|
ab2eac47c0 |
Update login screen and site settings with editable footer and AI art
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 |
||
|
|
abcc32fb66 |
Add ability to reorder apps on the home screen
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 |
||
|
|
99f2c84a84 |
Admin-controlled public registration toggle
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. |
||
|
|
397a384785 |
Add editable site name and finish service image upload
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. |
||
|
|
001e9023b2 |
feat: build TeaBoy OS — bilingual Arabic/English internal OS platform
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 |
||
|
|
8337c4b212 | Initial commit |