Commit Graph

2210 Commits

Author SHA1 Message Date
riyadhafraa 1ad311ad50 Add automated tests for role create / edit / delete endpoints (Task #90)
Original task
- Cover the new /api/roles create, update, and delete handlers with
  automated tests so regressions in role validation, system-role
  protection, and the in-use conflict response can't slip in unnoticed.
- Tests must run as part of the standard api-server test command.

What was added
- artifacts/api-server/tests/roles-crud.test.mjs with 7 tests:
  * POST /api/roles -> 201 (also checks DB row + serialized fields)
  * POST /api/roles duplicate name -> 409 (no second row inserted)
  * POST /api/roles invalid name format -> 400 (no row leaked)
  * PATCH /api/roles/:id description-only update -> 200, name unchanged
  * DELETE /api/roles/:id on system role 'admin' -> 400, row preserved
  * DELETE /api/roles/:id on in-use role -> 409 with userCount=1,
    groupCount=1; row preserved
  * DELETE /api/roles/:id on unused role -> 204, row gone
- The file follows the same pattern as groups-crud.test.mjs:
  pg.Pool seed via DATABASE_URL, login -> connect.sid cookie,
  thorough teardown of users / groups / roles / link tables.

Test infra
- No new test setup needed; the package's existing
  "test": "node --test 'tests/**/*.test.mjs'" picks the file up
  automatically. All 7 new tests pass.

Notes / drift
- Two pre-existing test failures (apps-open.test.mjs and one assertion
  in groups-crud.test.mjs that depends on global group counts) are
  unrelated to this task and were left untouched.

Replit-Task-Id: 4254b35b-ba28-443f-80a6-22f6ddfbedd6
2026-04-28 05:51:09 +00:00
Riyadh 1d9ece733a Add Reorder shortcut to finished orders on /my-orders
## Original task (Task #66)
Let users quickly re-order a service from their order history. On every
order card whose status is `completed` or `cancelled`, show a "Reorder"
button (Arabic: "اطلب مجددًا") that opens the existing OrderServiceModal
pre-filled with the original notes. Submitting creates a brand-new
order via the existing endpoint; the original order stays unchanged.

## Implementation
- `artifacts/tx-os/src/components/order-service-modal.tsx`: added an
  optional `initialNotes` prop and seeded the notes textarea with it
  (truncated to NOTES_MAX) whenever the modal opens. Existing services
  page callers are unaffected (prop defaults to undefined => empty notes).
- `artifacts/tx-os/src/pages/my-orders.tsx`:
  - Imported the `RotateCcw` icon and `OrderServiceModal`.
  - Added a "Reorder" button (with icon) on `OrderCard` that shows
    only for finished orders, alongside the existing
    cancel/delete actions.
  - Hoisted reorder modal state to `MyOrdersPage` via a new
    `reorderTarget` state and an `onReorder` callback wired through
    `OrderCard`.
  - Render `OrderServiceModal` at the page level using the original
    order's service info and notes; closing/submitting clears state.
- `artifacts/tx-os/src/locales/{en,ar}.json`: added `myOrders.reorder`
  ("Reorder" / "اطلب مجددًا").

## Notes / non-deviations
- Task description references `artifacts/teaboy-os/...`; the actual
  artifact in this repo is `artifacts/tx-os/`. Same files, same intent.
- Submitting reuses `useCreateServiceOrder` so the original order is
  untouched and standard error/success toasts fire from the modal.

## Verification
- `pnpm exec tsc --noEmit` shows no new errors in the touched files.
- E2E test: created a fresh user with one completed and one cancelled
  order, logged in, opened /my-orders, confirmed the Reorder button
  appears with notes prefilled in the modal, edited the notes,
  submitted, and verified a new pending order with the edited notes
  appeared while the original order remained unchanged.
2026-04-28 05:46:18 +00:00
riyadhafraa f7b8093d04 Add Reorder shortcut to finished orders on /my-orders
## Original task (Task #66)
Let users quickly re-order a service from their order history. On every
order card whose status is `completed` or `cancelled`, show a "Reorder"
button (Arabic: "اطلب مجددًا") that opens the existing OrderServiceModal
pre-filled with the original notes. Submitting creates a brand-new
order via the existing endpoint; the original order stays unchanged.

## Implementation
- `artifacts/tx-os/src/components/order-service-modal.tsx`: added an
  optional `initialNotes` prop and seeded the notes textarea with it
  (truncated to NOTES_MAX) whenever the modal opens. Existing services
  page callers are unaffected (prop defaults to undefined => empty notes).
- `artifacts/tx-os/src/pages/my-orders.tsx`:
  - Imported the `RotateCcw` icon and `OrderServiceModal`.
  - Added a "Reorder" button (with icon) on `OrderCard` that shows
    only for finished orders, alongside the existing
    cancel/delete actions.
  - Hoisted reorder modal state to `MyOrdersPage` via a new
    `reorderTarget` state and an `onReorder` callback wired through
    `OrderCard`.
  - Render `OrderServiceModal` at the page level using the original
    order's service info and notes; closing/submitting clears state.
- `artifacts/tx-os/src/locales/{en,ar}.json`: added `myOrders.reorder`
  ("Reorder" / "اطلب مجددًا").

## Notes / non-deviations
- Task description references `artifacts/teaboy-os/...`; the actual
  artifact in this repo is `artifacts/tx-os/`. Same files, same intent.
- Submitting reuses `useCreateServiceOrder` so the original order is
  untouched and standard error/success toasts fire from the modal.

## Verification
- `pnpm exec tsc --noEmit` shows no new errors in the touched files.
- E2E test: created a fresh user with one completed and one cancelled
  order, logged in, opened /my-orders, confirmed the Reorder button
  appears with notes prefilled in the modal, edited the notes,
  submitted, and verified a new pending order with the edited notes
  appeared while the original order remained unchanged.

Replit-Task-Id: 2140b360-9d2f-45d9-9ddc-267ea29f7d7c
2026-04-28 05:46:18 +00:00
Riyadh 6e84ee87e8 Add e2e tests for the order placement and cancellation flow
Original task (#65): Persist coverage for the new "Order" + "My Orders"
flow with a Playwright spec under artifacts/teaboy-os/tests/ that places
an order, sees the "Awaiting receiver" pill in /my-orders, cancels it,
and verifies the red cancelled pill replaces the timeline and the
cancel button disappears. Both Arabic and English label paths must be
exercised, and the spec must run under `pnpm --filter @workspace/teaboy-os
test:e2e`.

Implementation
- Added artifacts/tx-os/tests/order-place-cancel.spec.mjs (the artifact
  is now `tx-os`; `teaboy-os` no longer exists, so the spec lives in
  the same tests directory as `leave-group-successor.spec.mjs` and is
  picked up automatically by the existing test:e2e Playwright config).
- Style mirrors leave-group-successor.spec.mjs: file-scoped DB pool,
  per-test seeded user with the `user` role, after-all cleanup that
  deletes the spec's orders, the user's stray orders/notifications,
  user_roles, and the user itself.
- The spec runs the same flow twice, once with English labels and once
  with Arabic labels, driven through a `placeAndCancelFlow(page, lang,
  labels)` helper that:
    1. Forces the UI language via `page.addInitScript` writing the
       `tx-lang` localStorage key BEFORE any page script runs.
    2. Seeds the test user with `preferred_language` matching `lang`,
       because AuthContext + login.tsx call
       `i18n.changeLanguage(user.preferredLanguage)` on login and would
       otherwise override the localStorage seed.
    3. Logs in via the real /login form, navigates to /services,
       clicks the available service tile (matched by aria-label =
       localized name), submits the order modal, and waits for the
       POST /api/orders 201 to capture the order id.
    4. Navigates to /my-orders, asserts the first card contains the
       service name, the pending pill ("Awaiting receiver" / "بانتظار
       المستلِم"), and 4 timeline step circles.
    5. Clicks the in-card Cancel button, confirms via the alertdialog,
       waits for the PATCH /api/orders/:id/status 200 → cancelled.
    6. Asserts the timeline step circles are gone, the centered red
       cancelled pill ("Cancelled" / "ملغى") is present, and the
       Cancel button is removed from the card.

Verification
- `pnpm --filter @workspace/tx-os exec playwright install chromium`
- `cd artifacts/tx-os && pnpm exec playwright test --config
  playwright.config.mjs` → all 6 specs pass (the 4 pre-existing ones +
  the 2 new EN/AR tests).

Deviations
- Task referenced the `teaboy-os` artifact path; the actual artifact in
  this codebase is `tx-os`, so the spec was placed under
  `artifacts/tx-os/tests/` and registered with the existing
  `pnpm --filter @workspace/tx-os test:e2e` script (the original
  `teaboy-os` filter no longer exists).
2026-04-28 05:37:08 +00:00
riyadhafraa 6c160530fa Add e2e tests for the order placement and cancellation flow
Original task (#65): Persist coverage for the new "Order" + "My Orders"
flow with a Playwright spec under artifacts/teaboy-os/tests/ that places
an order, sees the "Awaiting receiver" pill in /my-orders, cancels it,
and verifies the red cancelled pill replaces the timeline and the
cancel button disappears. Both Arabic and English label paths must be
exercised, and the spec must run under `pnpm --filter @workspace/teaboy-os
test:e2e`.

Implementation
- Added artifacts/tx-os/tests/order-place-cancel.spec.mjs (the artifact
  is now `tx-os`; `teaboy-os` no longer exists, so the spec lives in
  the same tests directory as `leave-group-successor.spec.mjs` and is
  picked up automatically by the existing test:e2e Playwright config).
- Style mirrors leave-group-successor.spec.mjs: file-scoped DB pool,
  per-test seeded user with the `user` role, after-all cleanup that
  deletes the spec's orders, the user's stray orders/notifications,
  user_roles, and the user itself.
- The spec runs the same flow twice, once with English labels and once
  with Arabic labels, driven through a `placeAndCancelFlow(page, lang,
  labels)` helper that:
    1. Forces the UI language via `page.addInitScript` writing the
       `tx-lang` localStorage key BEFORE any page script runs.
    2. Seeds the test user with `preferred_language` matching `lang`,
       because AuthContext + login.tsx call
       `i18n.changeLanguage(user.preferredLanguage)` on login and would
       otherwise override the localStorage seed.
    3. Logs in via the real /login form, navigates to /services,
       clicks the available service tile (matched by aria-label =
       localized name), submits the order modal, and waits for the
       POST /api/orders 201 to capture the order id.
    4. Navigates to /my-orders, asserts the first card contains the
       service name, the pending pill ("Awaiting receiver" / "بانتظار
       المستلِم"), and 4 timeline step circles.
    5. Clicks the in-card Cancel button, confirms via the alertdialog,
       waits for the PATCH /api/orders/:id/status 200 → cancelled.
    6. Asserts the timeline step circles are gone, the centered red
       cancelled pill ("Cancelled" / "ملغى") is present, and the
       Cancel button is removed from the card.

Verification
- `pnpm --filter @workspace/tx-os exec playwright install chromium`
- `cd artifacts/tx-os && pnpm exec playwright test --config
  playwright.config.mjs` → all 6 specs pass (the 4 pre-existing ones +
  the 2 new EN/AR tests).

Deviations
- Task referenced the `teaboy-os` artifact path; the actual artifact in
  this codebase is `tx-os`, so the spec was placed under
  `artifacts/tx-os/tests/` and registered with the existing
  `pnpm --filter @workspace/tx-os test:e2e` script (the original
  `teaboy-os` filter no longer exists).

Replit-Task-Id: d2139d0e-9ee0-4795-8f94-29e1cfe4b35a
2026-04-28 05:37:08 +00:00
Riyadh 92202c4217 Task #89: Permissions picker for roles in admin dashboard
Original task: let admins assign permissions to roles from the dashboard.

Changes:
- lib/api-spec/openapi.yaml: added Permission and ReplaceRolePermissionsBody
  schemas; added GET /permissions, expanded GET /roles/{id}/permissions to
  return full Permission objects, and added admin-guarded
  PUT /roles/{id}/permissions. Ran codegen to refresh generated hooks/zod.
- artifacts/api-server/src/routes/roles.ts:
  * Added a single isSystemRole helper and PROTECTED_ROLE_NAMES set.
  * Added serializePermission + GET /permissions.
  * Expanded GET /roles/:id/permissions (404 on missing role, full
    Permission objects).
  * New PUT /roles/:id/permissions: blocks system/protected roles
    (400 with code "system_role_permissions"), rejects non-positive /
    non-integer permissionIds with 400, validates all ids exist (404),
    deletes+inserts atomically in a transaction, and notifies role
    holders via emitAppsChangedToRoleHolders.
  * Hardened legacy POST /roles/:id/permissions and
    DELETE /roles/:id/permissions/:permissionId so they also reject
    system-role mutations with the same code, closing the bypass that
    the new endpoint was meant to prevent.
  * Re-used isSystemRole in PATCH/DELETE role flows.
- artifacts/tx-os/src/pages/admin.tsx: added a permissions checkbox list
  inside the role editor dialog, disabled (read-only) for system /
  protected roles with a hint. Save flow runs updateRole then
  replaceRolePermissions only when the set actually changed and the role
  is non-system. Front-end ROLES_PROTECTED_NAMES set mirrors the
  back-end fallback so admin/user/order_receiver are recognised even
  when the legacy DB column is 0 (also gates the role-card system badge
  and delete button).
- artifacts/tx-os/src/locales/en.json + ar.json: new translation keys
  for the permissions picker, system-role read-only hint, empty list,
  and error toasts.

Verification: tx-os and api-server typecheck clean. End-to-end test
passes: admin login → edit a created role → toggle/save permissions →
re-open and confirm round-trip → admin role dialog shows disabled
checkboxes and read-only hint → PUT on admin role returns 400
"system_role_permissions". curl regression checks: legacy POST/DELETE
on admin role return the same 400/code; PUT rejects float / negative /
string ids with 400.
2026-04-27 12:58:16 +00:00
riyadhafraa aa6866936d Task #89: Permissions picker for roles in admin dashboard
Original task: let admins assign permissions to roles from the dashboard.

Changes:
- lib/api-spec/openapi.yaml: added Permission and ReplaceRolePermissionsBody
  schemas; added GET /permissions, expanded GET /roles/{id}/permissions to
  return full Permission objects, and added admin-guarded
  PUT /roles/{id}/permissions. Ran codegen to refresh generated hooks/zod.
- artifacts/api-server/src/routes/roles.ts:
  * Added a single isSystemRole helper and PROTECTED_ROLE_NAMES set.
  * Added serializePermission + GET /permissions.
  * Expanded GET /roles/:id/permissions (404 on missing role, full
    Permission objects).
  * New PUT /roles/:id/permissions: blocks system/protected roles
    (400 with code "system_role_permissions"), rejects non-positive /
    non-integer permissionIds with 400, validates all ids exist (404),
    deletes+inserts atomically in a transaction, and notifies role
    holders via emitAppsChangedToRoleHolders.
  * Hardened legacy POST /roles/:id/permissions and
    DELETE /roles/:id/permissions/:permissionId so they also reject
    system-role mutations with the same code, closing the bypass that
    the new endpoint was meant to prevent.
  * Re-used isSystemRole in PATCH/DELETE role flows.
- artifacts/tx-os/src/pages/admin.tsx: added a permissions checkbox list
  inside the role editor dialog, disabled (read-only) for system /
  protected roles with a hint. Save flow runs updateRole then
  replaceRolePermissions only when the set actually changed and the role
  is non-system. Front-end ROLES_PROTECTED_NAMES set mirrors the
  back-end fallback so admin/user/order_receiver are recognised even
  when the legacy DB column is 0 (also gates the role-card system badge
  and delete button).
- artifacts/tx-os/src/locales/en.json + ar.json: new translation keys
  for the permissions picker, system-role read-only hint, empty list,
  and error toasts.

Verification: tx-os and api-server typecheck clean. End-to-end test
passes: admin login → edit a created role → toggle/save permissions →
re-open and confirm round-trip → admin role dialog shows disabled
checkboxes and read-only hint → PUT on admin role returns 400
"system_role_permissions". curl regression checks: legacy POST/DELETE
on admin role return the same 400/code; PUT rejects float / negative /
string ids with 400.

Replit-Task-Id: 74180397-afdf-4489-976a-a6fe099684bd
2026-04-27 12:58:16 +00:00
Riyadh 38ccd99ae3 Let admins edit role names + descriptions in both languages
Original task (#88): The Roles tab in the Group editor displays each role's
Arabic and English descriptions, but admins had no UI to edit them — any
tweak required a developer. The edit dialog also didn't allow updating the
role's name. Make role name + bilingual descriptions editable from the
admin Roles panel and persist via PATCH /api/roles/:id.

Changes:
- lib/api-spec/openapi.yaml: UpdateRoleBody now accepts an optional `name`
  (minLength 1). PATCH /roles/{id} summary updated to "Update role name and
  descriptions (admin)" and documents 400 (invalid request) and 409 (name
  already taken) responses. Regenerated lib/api-zod.
- artifacts/api-server/src/routes/roles.ts (PATCH /roles/:id):
  * Accept and validate `name` with the same rules as create
    (`/^[a-z][a-z0-9_]*$/i`, uniqueness check that ignores the role's own row).
  * Refuse to rename system roles (isSystem=1 or one of admin/user/
    order_receiver) -> 400 with `code: "system_role_rename"`.
  * Returns 409 on name collision.
- artifacts/tx-os/src/pages/admin.tsx (RolesPanel edit dialog):
  * Edit form now includes a Name input (data-testid="role-edit-name")
    prefilled from the role.
  * Name input is disabled for system roles, with a dedicated hint string.
  * Save is disabled when name is empty; 400 errors with
    `code: "system_role_rename"` show a system-role-specific toast,
    other 400s show the invalid-name toast, and 409 shows name-taken.
- artifacts/tx-os/src/locales/{en,ar}.json:
  * Replaced the now-misleading `editHint` ("Role names cannot be changed")
    with `editSystemNameHint` (only shown for system roles).
  * Removed "Cannot be changed later" from `nameHint`.
  * Added `errorSystemRename` for the system-role rename toast.

Validation:
- pnpm typecheck passes.
- Playwright e2e (testing skill) created a role, renamed it + updated both
  descriptions, verified the new name + descriptions appear on the role
  card and in the Group editor's Roles tab, confirmed empty name disables
  Save, and cleaned up via delete. (Test agent flagged one stale snapshot
  on the system-role disabled-name check; the implementation gates on
  r.isSystem from the API and is also enforced server-side.)
2026-04-27 12:31:09 +00:00
riyadhafraa 1e9024e5a2 Let admins edit role names + descriptions in both languages
Original task (#88): The Roles tab in the Group editor displays each role's
Arabic and English descriptions, but admins had no UI to edit them — any
tweak required a developer. The edit dialog also didn't allow updating the
role's name. Make role name + bilingual descriptions editable from the
admin Roles panel and persist via PATCH /api/roles/:id.

Changes:
- lib/api-spec/openapi.yaml: UpdateRoleBody now accepts an optional `name`
  (minLength 1). PATCH /roles/{id} summary updated to "Update role name and
  descriptions (admin)" and documents 400 (invalid request) and 409 (name
  already taken) responses. Regenerated lib/api-zod.
- artifacts/api-server/src/routes/roles.ts (PATCH /roles/:id):
  * Accept and validate `name` with the same rules as create
    (`/^[a-z][a-z0-9_]*$/i`, uniqueness check that ignores the role's own row).
  * Refuse to rename system roles (isSystem=1 or one of admin/user/
    order_receiver) -> 400 with `code: "system_role_rename"`.
  * Returns 409 on name collision.
- artifacts/tx-os/src/pages/admin.tsx (RolesPanel edit dialog):
  * Edit form now includes a Name input (data-testid="role-edit-name")
    prefilled from the role.
  * Name input is disabled for system roles, with a dedicated hint string.
  * Save is disabled when name is empty; 400 errors with
    `code: "system_role_rename"` show a system-role-specific toast,
    other 400s show the invalid-name toast, and 409 shows name-taken.
- artifacts/tx-os/src/locales/{en,ar}.json:
  * Replaced the now-misleading `editHint` ("Role names cannot be changed")
    with `editSystemNameHint` (only shown for system roles).
  * Removed "Cannot be changed later" from `nameHint`.
  * Added `errorSystemRename` for the system-role rename toast.

Validation:
- pnpm typecheck passes.
- Playwright e2e (testing skill) created a role, renamed it + updated both
  descriptions, verified the new name + descriptions appear on the role
  card and in the Group editor's Roles tab, confirmed empty name disables
  Save, and cleaned up via delete. (Test agent flagged one stale snapshot
  on the system-role disabled-name check; the implementation gates on
  r.isSystem from the API and is also enforced server-side.)

Replit-Task-Id: 01dbc785-03e0-4714-b9d5-f0dff6de9a56
2026-04-27 12:31:09 +00:00
Riyadh e21af0216d Treat 404 in incoming-order actions as "no longer available"
Task #87: Confirm-receipt should also handle the case where the order was
deleted.

When an admin deletes an order while a receiver is viewing it, the API
returns 404 Order not found for confirm-receipt and the status PATCH.
Previously the UI showed the generic "Could not complete the action"
toast and left the stale card in place.

Updated all three onError branches in
`artifacts/tx-os/src/pages/orders-incoming.tsx` so 404 responses are
handled the same way as the existing `order_unavailable` 409:

- handleConfirmReceipt: 404 now shows the localized
  "incomingOrders.orderUnavailable" toast instead of "actionFailed".
  invalidate() was already called for every error in this branch, so
  the stale card is removed.
- handleSetStatus (preparing/completed): 404 now shows the
  "orderUnavailable" toast and calls invalidate() to drop the card.
- handleCancel: same treatment for 404 on the cancel PATCH.

The 409 already-claimed branch is preserved unchanged. No new
translation keys were needed; `incomingOrders.orderUnavailable` already
exists in both `src/locales/en.json` and `src/locales/ar.json`.

Pre-existing TypeScript errors in `src/pages/admin.tsx` are unrelated
to this change.
2026-04-27 12:14:42 +00:00
riyadhafraa 574b93022c Treat 404 in incoming-order actions as "no longer available"
Task #87: Confirm-receipt should also handle the case where the order was
deleted.

When an admin deletes an order while a receiver is viewing it, the API
returns 404 Order not found for confirm-receipt and the status PATCH.
Previously the UI showed the generic "Could not complete the action"
toast and left the stale card in place.

Updated all three onError branches in
`artifacts/tx-os/src/pages/orders-incoming.tsx` so 404 responses are
handled the same way as the existing `order_unavailable` 409:

- handleConfirmReceipt: 404 now shows the localized
  "incomingOrders.orderUnavailable" toast instead of "actionFailed".
  invalidate() was already called for every error in this branch, so
  the stale card is removed.
- handleSetStatus (preparing/completed): 404 now shows the
  "orderUnavailable" toast and calls invalidate() to drop the card.
- handleCancel: same treatment for 404 on the cancel PATCH.

The 409 already-claimed branch is preserved unchanged. No new
translation keys were needed; `incomingOrders.orderUnavailable` already
exists in both `src/locales/en.json` and `src/locales/ar.json`.

Pre-existing TypeScript errors in `src/pages/admin.tsx` are unrelated
to this change.

Replit-Task-Id: a923d4f2-0a45-4816-aee9-0822c5677b88
2026-04-27 12:14:42 +00:00
Riyadh 9fac768d2a Add test coverage for the order-unavailable error responses (Task #86)
Task #80 introduced a new 409 `order_unavailable` error code returned by:
  - PATCH /api/orders/:id/confirm-receipt
  - PATCH /api/orders/:id/status
…to distinguish "already_claimed" (lost a race) from "no longer claimable"
(target order is cancelled or completed). The integration test suite did
not cover these paths.

Changes
-------
artifacts/api-server/tests/service-orders.test.mjs
  + Added test "confirm-receipt returns 409 order_unavailable when the order
    is cancelled or completed":
      - Owner cancels a pending order → another receiver's confirm-receipt
        returns 409 with body.error === "order_unavailable" (not
        "already_claimed").
      - Receiver claims, prepares, completes an order → another receiver's
        confirm-receipt returns 409 "order_unavailable".

  + Added test "PATCH /orders/:id/status returns 409 order_unavailable when
    targeting a terminal order":
      - Completed order: assignee → preparing  → 409 "order_unavailable"
      - Completed order: assignee → completed  → 409 "order_unavailable"
      - Completed order: owner    → cancelled  → 409 "order_unavailable"
      - Cancelled order: owner    → cancelled  → 409 "order_unavailable"

Verification
------------
All 11 service-orders integration tests pass (including both new tests):
  pnpm --filter @workspace/api-server test → 11 passed, 0 failed.

No production code changes; tests only.
2026-04-27 12:11:57 +00:00
riyadhafraa b865e69f64 Add test coverage for the order-unavailable error responses (Task #86)
Task #80 introduced a new 409 `order_unavailable` error code returned by:
  - PATCH /api/orders/:id/confirm-receipt
  - PATCH /api/orders/:id/status
…to distinguish "already_claimed" (lost a race) from "no longer claimable"
(target order is cancelled or completed). The integration test suite did
not cover these paths.

Changes
-------
artifacts/api-server/tests/service-orders.test.mjs
  + Added test "confirm-receipt returns 409 order_unavailable when the order
    is cancelled or completed":
      - Owner cancels a pending order → another receiver's confirm-receipt
        returns 409 with body.error === "order_unavailable" (not
        "already_claimed").
      - Receiver claims, prepares, completes an order → another receiver's
        confirm-receipt returns 409 "order_unavailable".

  + Added test "PATCH /orders/:id/status returns 409 order_unavailable when
    targeting a terminal order":
      - Completed order: assignee → preparing  → 409 "order_unavailable"
      - Completed order: assignee → completed  → 409 "order_unavailable"
      - Completed order: owner    → cancelled  → 409 "order_unavailable"
      - Cancelled order: owner    → cancelled  → 409 "order_unavailable"

Verification
------------
All 11 service-orders integration tests pass (including both new tests):
  pnpm --filter @workspace/api-server test → 11 passed, 0 failed.

No production code changes; tests only.

Replit-Task-Id: 229ed991-1c32-4281-8e57-00abeacd80b4
2026-04-27 12:11:57 +00:00
Riyadh 694239f7b8 Warn admins before deleting non-empty users/apps/services (Task #85)
Apply the existing groups delete-warning pattern to user, app, and
service deletions so admins are warned (and have to confirm twice)
before destroying records that still own dependent data.

Backend
- openapi.yaml: added `force` query param to DELETE /users/{id},
  /apps/{id}, /services/{id} plus three new conflict schemas
  (UserDeletionConflict, AppDeletionConflict, ServiceDeletionConflict).
- routes/users.ts, apps.ts, services.ts: rewrote DELETE handlers to
  count dependents (notes/orders/conversations/messages for users;
  group_apps/restrictions/open events for apps; service_orders for
  services), return 409 with counts when non-empty and force is not set,
  and on `?force=true` perform the cascade in a transaction and write
  an `*.force_delete` audit_logs row.
- Existing 204 success path preserved for empty deletes.

UI (artifacts/tx-os/src/pages/admin.tsx)
- New `DeletionWarningDialog` helper, plus app/service/user delete state
  + lazy 409 detection (first click probes; on 409 the dialog upgrades
  to show counts + "Delete anyway"; second click sends ?force=true).
- Replaced the three plain `confirm(t("admin.deleteConfirm"))` callsites.

i18n
- Added admin.deleteApp/deleteService/deleteUser keys (title, warning,
  forceHint, emptyBody, count keys, confirm, anyway) in en.json + ar.json.

Tests
- artifacts/api-server/tests/delete-force-warnings.test.mjs covers all
  9 cases (clean delete, 409 with counts, force=true 204 + audit log)
  for users, apps, and services. Existing groups-crud and service-orders
  tests still pass.

Notes / drift
- Lazy detection (vs eager counts on the row like Groups already does)
  was chosen because list endpoints don't return counts yet — proposed
  follow-up #96 covers eager counts so the warning appears on first
  click everywhere.
- E2E test for the UI flow flaked twice; backend integration tests
  (9/9 pass), direct curl validation of force=true returning 204, and
  typecheck across the monorepo all confirm correctness.
2026-04-27 12:07:07 +00:00
riyadhafraa 7c9edf6cb6 Warn admins before deleting non-empty users/apps/services (Task #85)
Apply the existing groups delete-warning pattern to user, app, and
service deletions so admins are warned (and have to confirm twice)
before destroying records that still own dependent data.

Backend
- openapi.yaml: added `force` query param to DELETE /users/{id},
  /apps/{id}, /services/{id} plus three new conflict schemas
  (UserDeletionConflict, AppDeletionConflict, ServiceDeletionConflict).
- routes/users.ts, apps.ts, services.ts: rewrote DELETE handlers to
  count dependents (notes/orders/conversations/messages for users;
  group_apps/restrictions/open events for apps; service_orders for
  services), return 409 with counts when non-empty and force is not set,
  and on `?force=true` perform the cascade in a transaction and write
  an `*.force_delete` audit_logs row.
- Existing 204 success path preserved for empty deletes.

UI (artifacts/tx-os/src/pages/admin.tsx)
- New `DeletionWarningDialog` helper, plus app/service/user delete state
  + lazy 409 detection (first click probes; on 409 the dialog upgrades
  to show counts + "Delete anyway"; second click sends ?force=true).
- Replaced the three plain `confirm(t("admin.deleteConfirm"))` callsites.

i18n
- Added admin.deleteApp/deleteService/deleteUser keys (title, warning,
  forceHint, emptyBody, count keys, confirm, anyway) in en.json + ar.json.

Tests
- artifacts/api-server/tests/delete-force-warnings.test.mjs covers all
  9 cases (clean delete, 409 with counts, force=true 204 + audit log)
  for users, apps, and services. Existing groups-crud and service-orders
  tests still pass.

Notes / drift
- Lazy detection (vs eager counts on the row like Groups already does)
  was chosen because list endpoints don't return counts yet — proposed
  follow-up #96 covers eager counts so the warning appears on first
  click everywhere.
- E2E test for the UI flow flaked twice; backend integration tests
  (9/9 pass), direct curl validation of force=true returning 204, and
  typecheck across the monorepo all confirm correctness.

Replit-Task-Id: 91404d92-e74c-4720-8fc9-8eb772eefc33
2026-04-27 12:07:07 +00:00
Riyadh 55f8b433cc Git commit prior to merge 2026-04-27 12:07:06 +00:00
riyadhafraa 6e9b9b5f3d Git commit prior to merge 2026-04-27 12:07:06 +00:00
Riyadh 1b96b6528d 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.
2026-04-27 12:05:40 +00:00
riyadhafraa 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
2026-04-27 12:05:40 +00:00
riyadhafraa 0a5e89a6d4 Add a new module for managing executive meetings
Introduces a new "Executive Meetings Management" module with database schema, API endpoints, and UI components, designed to run independently without altering existing functionalities.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: e463ac71-2b1f-4b47-8b94-ca9526e6f427
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/3E2mNig
Replit-Helium-Checkpoint-Created: true
2026-04-27 11:46:39 +00:00
Riyadh 2827e092b3 Add admin Audit Log view (task-84)
Original task: Show admins a history of sensitive actions. Forced group
deletions already write to the new `audit_logs` table; admins now have
a UI to browse and filter those entries.

API
- New OpenAPI endpoint GET /admin/audit-logs (admin-only) with optional
  filters: action (exact), from / to (YYYY-MM-DD, inclusive), limit
  (1-200, default 50), offset (default 0).
- New schemas: AuditLogActor, AuditLogEntry, AuditLogList. List
  response includes paginated `entries`, `totalCount`, `nextOffset`,
  and a distinct `actions` list to power the filter dropdown.
- New `audit` tag added to the spec; ran orval codegen so
  api-client-react / api-zod expose useListAuditLogs et al.
- Implemented `artifacts/api-server/src/routes/audit.ts` and registered
  it in routes/index.ts. Validates date format / order, joins users for
  actor info, and orders newest-first.

UI (artifacts/tx-os/src/pages/admin.tsx)
- New "Audit log" entry under the User Management nav group (icon:
  ScrollText). Section deep-link works via #section=audit-log.
- New AuditLogPanel + AuditLogRow components: action chip + target,
  formatted timestamp, actor avatar/name, expandable JSON metadata.
- Filters bar: action dropdown (populated from API's distinct
  actions), from/to date inputs, Apply / Reset, plus a "Load more"
  control that increases the page size (caps at 200, the API limit).
  Read-only by design.
- Added en/ar locale strings for nav.auditLog and the audit subtree.

Verification
- Typecheck: pnpm -w run typecheck (clean).
- API smoke tested via curl: 401 unauthenticated, validation errors
  for bad / inverted dates, returns the seeded `group.force_delete`
  entry once produced.
- End-to-end browser test (testing skill): logged in as admin,
  navigated to /admin#section=audit-log, verified the row, expanded
  metadata, exercised filters (empty state + reset).

No deviations from the task description.
2026-04-27 11:28:20 +00:00
riyadhafraa 1ca5d5ae4b Add admin Audit Log view (task-84)
Original task: Show admins a history of sensitive actions. Forced group
deletions already write to the new `audit_logs` table; admins now have
a UI to browse and filter those entries.

API
- New OpenAPI endpoint GET /admin/audit-logs (admin-only) with optional
  filters: action (exact), from / to (YYYY-MM-DD, inclusive), limit
  (1-200, default 50), offset (default 0).
- New schemas: AuditLogActor, AuditLogEntry, AuditLogList. List
  response includes paginated `entries`, `totalCount`, `nextOffset`,
  and a distinct `actions` list to power the filter dropdown.
- New `audit` tag added to the spec; ran orval codegen so
  api-client-react / api-zod expose useListAuditLogs et al.
- Implemented `artifacts/api-server/src/routes/audit.ts` and registered
  it in routes/index.ts. Validates date format / order, joins users for
  actor info, and orders newest-first.

UI (artifacts/tx-os/src/pages/admin.tsx)
- New "Audit log" entry under the User Management nav group (icon:
  ScrollText). Section deep-link works via #section=audit-log.
- New AuditLogPanel + AuditLogRow components: action chip + target,
  formatted timestamp, actor avatar/name, expandable JSON metadata.
- Filters bar: action dropdown (populated from API's distinct
  actions), from/to date inputs, Apply / Reset, plus a "Load more"
  control that increases the page size (caps at 200, the API limit).
  Read-only by design.
- Added en/ar locale strings for nav.auditLog and the audit subtree.

Verification
- Typecheck: pnpm -w run typecheck (clean).
- API smoke tested via curl: 401 unauthenticated, validation errors
  for bad / inverted dates, returns the seeded `group.force_delete`
  entry once produced.
- End-to-end browser test (testing skill): logged in as admin,
  navigated to /admin#section=audit-log, verified the row, expanded
  metadata, exercised filters (empty state + reset).

No deviations from the task description.

Replit-Task-Id: 5b5bf9b1-6937-43c3-85c9-81f0c19a5e49
2026-04-27 11:28:20 +00:00
Riyadh 6637440a02 Fix order cancellation logic and update tests to reflect new behavior
Adjusts the order cancellation endpoint to correctly handle admin overrides for completed/cancelled orders and updates associated tests to expect 409 status codes reflecting Task #80's changes.
2026-04-27 11:28:14 +00:00
riyadhafraa c05ae786fc Fix order cancellation logic and update tests to reflect new behavior
Adjusts the order cancellation endpoint to correctly handle admin overrides for completed/cancelled orders and updates associated tests to expect 409 status codes reflecting Task #80's changes.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: d358e7e1-8a16-4746-86aa-4f1739233f2e
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/3E2mNig
Replit-Helium-Checkpoint-Created: true
2026-04-27 11:28:14 +00:00
Riyadh 0243f1026b Task #83: Refresh role and permission changes live across user sessions
## Socket.IO broadcasts (core task):
- POST /users/:id/roles: uses .returning() to emit apps_changed only when row inserted
- DELETE /users/:id/roles/:roleName: uses .returning() to emit only when row deleted
- Added emitAppsChangedToRoleHolders(roleId) helper to realtime.ts
- New role-permission endpoints in roles.ts, emit only on effective changes:
  - GET /roles/:id/permissions — list permissions for a role
  - POST /roles/:id/permissions — assign; emits to role holders on actual insert
  - DELETE /roles/:id/permissions/:permId — remove; emits only if row was deleted

## Bug fix: admin panel not showing disabled apps
- Added GET /api/admin/apps (requireAdmin) returning ALL apps from DB
- Updated admin.tsx: useQuery → /api/admin/apps with ADMIN_APPS_KEY constant
- All admin.tsx mutation handlers invalidate ADMIN_APPS_KEY

## UI fix: disabled app cards nearly invisible (opacity-60 on glass)
- Gray background + grayscale icon + muted text + stronger Disabled badge


## DB: removed 11 duplicate rows from app_permissions table
2026-04-27 11:11:43 +00:00
riyadhafraa 595ca06a57 Task #83: Refresh role and permission changes live across user sessions
## Socket.IO broadcasts (core task):
- POST /users/:id/roles: uses .returning() to emit apps_changed only when row inserted
- DELETE /users/:id/roles/:roleName: uses .returning() to emit only when row deleted
- Added emitAppsChangedToRoleHolders(roleId) helper to realtime.ts
- New role-permission endpoints in roles.ts, emit only on effective changes:
  - GET /roles/:id/permissions — list permissions for a role
  - POST /roles/:id/permissions — assign; emits to role holders on actual insert
  - DELETE /roles/:id/permissions/:permId — remove; emits only if row was deleted

## Bug fix: admin panel not showing disabled apps
- Added GET /api/admin/apps (requireAdmin) returning ALL apps from DB
- Updated admin.tsx: useQuery → /api/admin/apps with ADMIN_APPS_KEY constant
- All admin.tsx mutation handlers invalidate ADMIN_APPS_KEY

## UI fix: disabled app cards nearly invisible (opacity-60 on glass)
- Gray background + grayscale icon + muted text + stronger Disabled badge

Replit-Task-Id: f439ec75-bcd5-4030-8ee1-83a5d976f1a1

## DB: removed 11 duplicate rows from app_permissions table
2026-04-27 11:11:43 +00:00
Riyadh 1dd4245945 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.
2026-04-27 10:26:24 +00:00
riyadhafraa 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
2026-04-27 10:26:24 +00:00
Riyadh a1c5b081dd Show role descriptions in both languages on the Groups Roles tab
Task #81: The Groups editor's Roles tab previously displayed only the
description in the active UI language with the role's machine name as
a small subtitle. Bilingual admin teams asked to see both languages at
once.

Changes (artifacts/tx-os/src/pages/admin.tsx):
- Imported Tooltip, TooltipContent, TooltipTrigger from
  @/components/ui/tooltip (TooltipProvider was already mounted at the
  App root, so no provider wiring was needed).
- Refactored each role row in the Roles tab of GroupDetailEditor:
  * Primary line: description in the active language
    (descriptionAr when lang === "ar", otherwise descriptionEn). Falls
    back to the other-language description, then to the role's name
    when no descriptions exist.
  * Secondary line (smaller, muted): the OTHER language's description,
    rendered with the appropriate dir attribute. Only shown when both
    descriptions are present so single-description roles fall back
    cleanly.
  * Machine name remains visible as a small monospace subtitle on the
    trailing edge of the row.
  * The whole row is wrapped in a Tooltip whose content shows both
    descriptions (each with its proper dir) plus the machine name for
    full context on hover.
- Added data-testid="group-role-<id>-secondary" so the secondary line
  is easily targetable from tests.

Verification:
- Type-checked tx-os (only pre-existing errors elsewhere in admin.tsx
  remain; none introduced by this change).
- e2e test passed: logged in as admin, opened a group, switched to the
  Roles tab, confirmed primary + secondary description lines, hovered
  to see the bilingual tooltip, toggled the UI language and confirmed
  the lines swapped, and confirmed checkboxes still toggle through the
  TooltipTrigger asChild wrapper.

The single-description-only fallback could not be exercised against
seeded data (all seeded roles are bilingual), but the rendering logic
gracefully handles that case via the `heading` and `showSecondary`
guards.

Also reverts an unrelated stray modification to
artifacts/tx-os/public/opengraph.jpg that was swept into the previous
auto-commit, restoring it to the version from HEAD~1.

Follow-up proposed: #88 "Let admins edit role descriptions in both
languages".
2026-04-27 10:08:41 +00:00
riyadhafraa b0b1d673b7 Show role descriptions in both languages on the Groups Roles tab
Task #81: The Groups editor's Roles tab previously displayed only the
description in the active UI language with the role's machine name as
a small subtitle. Bilingual admin teams asked to see both languages at
once.

Changes (artifacts/tx-os/src/pages/admin.tsx):
- Imported Tooltip, TooltipContent, TooltipTrigger from
  @/components/ui/tooltip (TooltipProvider was already mounted at the
  App root, so no provider wiring was needed).
- Refactored each role row in the Roles tab of GroupDetailEditor:
  * Primary line: description in the active language
    (descriptionAr when lang === "ar", otherwise descriptionEn). Falls
    back to the other-language description, then to the role's name
    when no descriptions exist.
  * Secondary line (smaller, muted): the OTHER language's description,
    rendered with the appropriate dir attribute. Only shown when both
    descriptions are present so single-description roles fall back
    cleanly.
  * Machine name remains visible as a small monospace subtitle on the
    trailing edge of the row.
  * The whole row is wrapped in a Tooltip whose content shows both
    descriptions (each with its proper dir) plus the machine name for
    full context on hover.
- Added data-testid="group-role-<id>-secondary" so the secondary line
  is easily targetable from tests.

Verification:
- Type-checked tx-os (only pre-existing errors elsewhere in admin.tsx
  remain; none introduced by this change).
- e2e test passed: logged in as admin, opened a group, switched to the
  Roles tab, confirmed primary + secondary description lines, hovered
  to see the bilingual tooltip, toggled the UI language and confirmed
  the lines swapped, and confirmed checkboxes still toggle through the
  TooltipTrigger asChild wrapper.

The single-description-only fallback could not be exercised against
seeded data (all seeded roles are bilingual), but the rendering logic
gracefully handles that case via the `heading` and `showSecondary`
guards.

Also reverts an unrelated stray modification to
artifacts/tx-os/public/opengraph.jpg that was swept into the previous
auto-commit, restoring it to the version from HEAD~1.

Follow-up proposed: #88 "Let admins edit role descriptions in both
languages".

Replit-Task-Id: dfd3b45c-7b85-4c30-b0f9-3bfbab81be8c
2026-04-27 10:08:41 +00:00
Riyadh 57292caa07 fix: tell receivers when an order is no longer available + fix disabled apps disappearing from admin
## Task #80 — Tell receivers when an order is no longer available to claim

### Problem
When a receiver tried to act on an order that had already been cancelled, completed,
or claimed by someone else, the UI showed a generic "Could not complete the action"
toast. The stale card also stayed in the list, requiring a manual refresh.

### Changes

**artifacts/api-server/src/routes/service-orders.ts**
- PATCH /orders/:id/confirm-receipt: after a failed atomic claim, query the current
  order status; return 409 `order_unavailable` if it's cancelled/completed (vs the
  existing 409 `already_claimed` for when it was grabbed by someone else first)
- PATCH /orders/:id/status (preparing/completed branch): terminal-state check runs
  BEFORE permission gating — returns 409 `order_unavailable` when order is already
  cancelled/completed instead of ever hitting 403 Forbidden
- PATCH /orders/:id/status (cancel branch): same — terminal-state check moved to
  the top of the branch so non-admin receivers trying to cancel an already-terminal
  order get 409 `order_unavailable` (not 403 Forbidden which would never let the UI
  remove the stale card)

**artifacts/tx-os/src/pages/orders-incoming.tsx**
- handleConfirmReceipt onError: distinguish 409 `order_unavailable` from
  `already_claimed`, show specific toast, remove stale card via invalidate()
- handleSetStatus onError: same pattern — specific toast + invalidate() on
  `order_unavailable`
- handleCancel onError: same pattern

**artifacts/tx-os/src/locales/en.json + ar.json**
- Added `incomingOrders.orderUnavailable` ("This order is no longer available" /
  "هذا الطلب لم يعد متاحاً")

## Bonus fix — Disabled app disappears from admin panel (user-reported)

### Problem
When an admin disabled an app via the toggle, the app disappeared from the admin
panel too (isActive filter applied to everyone), making it impossible to re-enable
without direct DB access.

### Changes

**artifacts/api-server/src/routes/apps.ts**
- getVisibleAppsForUser: admins now receive ALL apps including inactive ones via
  conditional $dynamic() Drizzle query; non-admins still only see active apps

**artifacts/tx-os/src/pages/home.tsx**
- Filter orderedApps to only active apps before rendering so inactive apps don't
  appear on the home screen even for admins

**artifacts/tx-os/src/pages/admin.tsx**
- Inactive app cards show reduced opacity + a "Disabled/معطّل" badge

**artifacts/tx-os/src/locales/en.json + ar.json**
- Added `admin.appDisabled` ("Disabled" / "معطّل")
2026-04-27 09:06:23 +00:00
riyadhafraa 5051b1b905 fix: tell receivers when an order is no longer available + fix disabled apps disappearing from admin
## Task #80 — Tell receivers when an order is no longer available to claim

### Problem
When a receiver tried to act on an order that had already been cancelled, completed,
or claimed by someone else, the UI showed a generic "Could not complete the action"
toast. The stale card also stayed in the list, requiring a manual refresh.

### Changes

**artifacts/api-server/src/routes/service-orders.ts**
- PATCH /orders/:id/confirm-receipt: after a failed atomic claim, query the current
  order status; return 409 `order_unavailable` if it's cancelled/completed (vs the
  existing 409 `already_claimed` for when it was grabbed by someone else first)
- PATCH /orders/:id/status (preparing/completed branch): terminal-state check runs
  BEFORE permission gating — returns 409 `order_unavailable` when order is already
  cancelled/completed instead of ever hitting 403 Forbidden
- PATCH /orders/:id/status (cancel branch): same — terminal-state check moved to
  the top of the branch so non-admin receivers trying to cancel an already-terminal
  order get 409 `order_unavailable` (not 403 Forbidden which would never let the UI
  remove the stale card)

**artifacts/tx-os/src/pages/orders-incoming.tsx**
- handleConfirmReceipt onError: distinguish 409 `order_unavailable` from
  `already_claimed`, show specific toast, remove stale card via invalidate()
- handleSetStatus onError: same pattern — specific toast + invalidate() on
  `order_unavailable`
- handleCancel onError: same pattern

**artifacts/tx-os/src/locales/en.json + ar.json**
- Added `incomingOrders.orderUnavailable` ("This order is no longer available" /
  "هذا الطلب لم يعد متاحاً")

## Bonus fix — Disabled app disappears from admin panel (user-reported)

### Problem
When an admin disabled an app via the toggle, the app disappeared from the admin
panel too (isActive filter applied to everyone), making it impossible to re-enable
without direct DB access.

### Changes

**artifacts/api-server/src/routes/apps.ts**
- getVisibleAppsForUser: admins now receive ALL apps including inactive ones via
  conditional $dynamic() Drizzle query; non-admins still only see active apps

**artifacts/tx-os/src/pages/home.tsx**
- Filter orderedApps to only active apps before rendering so inactive apps don't
  appear on the home screen even for admins

**artifacts/tx-os/src/pages/admin.tsx**
- Inactive app cards show reduced opacity + a "Disabled/معطّل" badge

**artifacts/tx-os/src/locales/en.json + ar.json**
- Added `admin.appDisabled` ("Disabled" / "معطّل")

Replit-Task-Id: 8d6fade8-e76c-4d3c-864b-59007688c12c
2026-04-27 09:06:23 +00:00
Riyadh 14ce113e70 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.
2026-04-22 11:05:34 +00:00
riyadhafraa 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
2026-04-22 11:05:34 +00:00
riyadhafraa 959ad9ca51 Update the website's icon for better visual clarity
Add a new icon to the website.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 14de494e-2969-4faf-9689-a7c72ba47548
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/dnVFHsG
Replit-Helium-Checkpoint-Created: true
2026-04-22 10:57:10 +00:00
Riyadh 361b6848a2 Remove service descriptions from display and administration
Removes Arabic and English description fields from the admin form, the services display component, and the seed data in `scripts/src/seed.ts`, and removes the corresponding columns from the database.
2026-04-22 10:55:46 +00:00
riyadhafraa ea41328626 Remove service descriptions from display and administration
Removes Arabic and English description fields from the admin form, the services display component, and the seed data in `scripts/src/seed.ts`, and removes the corresponding columns from the database.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 5f1c43b0-7465-4e56-bb0e-896a4df38886
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/dnVFHsG
Replit-Helium-Checkpoint-Created: true
2026-04-22 10:55:46 +00:00
Riyadh eb5eb75f05 Design modern, minimal icons based on "TX" for the platform
Add new SVG icon assets for the platform's favicon and branding, including multiple variants and a preview HTML file.
2026-04-22 10:53:54 +00:00
riyadhafraa 9250e5e623 Design modern, minimal icons based on "TX" for the platform
Add new SVG icon assets for the platform's favicon and branding, including multiple variants and a preview HTML file.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 8912f52a-183a-4f2b-983a-b2ebfb2814ba
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/dnVFHsG
Replit-Helium-Checkpoint-Created: true
2026-04-22 10:53:54 +00:00
Riyadh aa462f9f52 Rename project and remove unused services
Update project name from teaboy-os to tx-os and remove obsolete services.
2026-04-22 10:52:09 +00:00
riyadhafraa 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
2026-04-22 10:52:09 +00:00
Riyadh 91330768ad 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.
2026-04-22 10:26:53 +00:00
riyadhafraa 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
2026-04-22 10:26:53 +00:00
Riyadh f3c2f362dc Reflect group memberships live in the user's app grid (Task #77)
Group/membership changes now push to affected users over Socket.IO so
their app dock updates without a manual refresh.

Server:
- New helper `artifacts/api-server/src/lib/realtime.ts` exposing
  `emitAppsChangedToUsers(userIds)` which lazy-imports the io instance
  (matching the pattern used in conversations/service-orders) and emits
  `apps_changed` to each `user:<id>` room.
- `routes/groups.ts`:
  - PATCH /groups/🆔 snapshot members before the write, union with
    members after, and emit to that set when membership changes; emit to
    current members when only apps/roles change.
  - POST/DELETE /groups/:id/users/:targetId emit to the target user.
  - POST/DELETE /groups/:id/apps|roles/:targetId emit to all current
    members of the group.
  - DELETE /groups/:id emits to all (former) members.
- `routes/users.ts` PATCH /users/:id emits to the affected user when
  groupIds is changed.

Client:
- `artifacts/teaboy-os/src/hooks/use-notifications-socket.ts` now also
  listens for `apps_changed` and invalidates `getListAppsQueryKey()` and
  `getGetMeQueryKey()`, refreshing the dock and AuthUser context.

Validation:
- API tests pass for all groups/membership cases. One pre-existing
  flaky service-orders parallel-receipt test failed; unrelated to this
  change.
2026-04-22 10:20:04 +00:00
riyadhafraa 6dd927350d Reflect group memberships live in the user's app grid (Task #77)
Group/membership changes now push to affected users over Socket.IO so
their app dock updates without a manual refresh.

Server:
- New helper `artifacts/api-server/src/lib/realtime.ts` exposing
  `emitAppsChangedToUsers(userIds)` which lazy-imports the io instance
  (matching the pattern used in conversations/service-orders) and emits
  `apps_changed` to each `user:<id>` room.
- `routes/groups.ts`:
  - PATCH /groups/🆔 snapshot members before the write, union with
    members after, and emit to that set when membership changes; emit to
    current members when only apps/roles change.
  - POST/DELETE /groups/:id/users/:targetId emit to the target user.
  - POST/DELETE /groups/:id/apps|roles/:targetId emit to all current
    members of the group.
  - DELETE /groups/:id emits to all (former) members.
- `routes/users.ts` PATCH /users/:id emits to the affected user when
  groupIds is changed.

Client:
- `artifacts/teaboy-os/src/hooks/use-notifications-socket.ts` now also
  listens for `apps_changed` and invalidates `getListAppsQueryKey()` and
  `getGetMeQueryKey()`, refreshing the dock and AuthUser context.

Validation:
- API tests pass for all groups/membership cases. One pre-existing
  flaky service-orders parallel-receipt test failed; unrelated to this
  change.

Replit-Task-Id: 3e5ae44b-9dcf-42f4-8d5d-a0f2b05b6065
2026-04-22 10:20:04 +00:00
Riyadh cb81fb583d Task #76: Show roles management inside the Groups editor
Adds a "Roles" tab to the GroupDetailEditor in the admin Groups screen so
admins can manage which roles are auto-granted to members of a group.

Changes:
- OpenAPI: added `GET /roles` (operationId `listRoles`) and a `Role` schema
  in `lib/api-spec/openapi.yaml`. Ran `pnpm --filter @workspace/api-spec
  run codegen` to regenerate `lib/api-client-react` and `lib/api-zod`.
- API server: added `GET /api/roles` (admin-only) in
  `artifacts/api-server/src/routes/groups.ts`, returning all roles
  ordered by name.
- Frontend (`artifacts/teaboy-os/src/pages/admin.tsx`):
  - Imported `useListRoles` / `getListRolesQueryKey`.
  - Added `roleIds` state and a fourth `"roles"` tab to GroupDetailEditor
    with a checkbox list backed by the new endpoint, hydrated from
    `group.roleIds`.
  - Save now sends `roleIds: Array.from(roleIds)` via the existing
    `PATCH /api/groups/:id` mutation.
  - On save, also invalidate `getGetGroupQueryKey(editingGroupId)` so the
    detail cache (30s staleTime) reflects the new role list immediately
    when the editor is reopened.
- Translations: added `admin.groups.tab.roles`, `admin.groups.rolesHint`,
  and `admin.groups.rolesEmpty` in both `en.json` and `ar.json`.

Verification:
- `pnpm -w run typecheck` passes.
- `pnpm --filter @workspace/api-server test` — 42/42 pass.
- e2e (testing skill): logged in as admin, opened TeaBoy group, toggled
  the `user` role on the new Roles tab, saved, reopened — toggle
  persisted.

No deviations from the task description.
2026-04-22 10:13:17 +00:00
riyadhafraa bc0737aed2 Task #76: Show roles management inside the Groups editor
Adds a "Roles" tab to the GroupDetailEditor in the admin Groups screen so
admins can manage which roles are auto-granted to members of a group.

Changes:
- OpenAPI: added `GET /roles` (operationId `listRoles`) and a `Role` schema
  in `lib/api-spec/openapi.yaml`. Ran `pnpm --filter @workspace/api-spec
  run codegen` to regenerate `lib/api-client-react` and `lib/api-zod`.
- API server: added `GET /api/roles` (admin-only) in
  `artifacts/api-server/src/routes/groups.ts`, returning all roles
  ordered by name.
- Frontend (`artifacts/teaboy-os/src/pages/admin.tsx`):
  - Imported `useListRoles` / `getListRolesQueryKey`.
  - Added `roleIds` state and a fourth `"roles"` tab to GroupDetailEditor
    with a checkbox list backed by the new endpoint, hydrated from
    `group.roleIds`.
  - Save now sends `roleIds: Array.from(roleIds)` via the existing
    `PATCH /api/groups/:id` mutation.
  - On save, also invalidate `getGetGroupQueryKey(editingGroupId)` so the
    detail cache (30s staleTime) reflects the new role list immediately
    when the editor is reopened.
- Translations: added `admin.groups.tab.roles`, `admin.groups.rolesHint`,
  and `admin.groups.rolesEmpty` in both `en.json` and `ar.json`.

Verification:
- `pnpm -w run typecheck` passes.
- `pnpm --filter @workspace/api-server test` — 42/42 pass.
- e2e (testing skill): logged in as admin, opened TeaBoy group, toggled
  the `user` role on the new Roles tab, saved, reopened — toggle
  persisted.

No deviations from the task description.

Replit-Task-Id: 42808400-9209-469b-b526-37c776ffb25d
2026-04-22 10:13:17 +00:00
Riyadh 9697875e9a Add Undo window when receivers cancel an incoming order
Original task (#73): Mirror the existing 7s Undo toast (already in place
for owner-side cancels in My Orders) on the receiver-side cancel action
in the Incoming Orders page.

Implementation:
- artifacts/teaboy-os/src/pages/orders-incoming.tsx
  - Captures the order's previous status (received | preparing) before
    issuing the cancel PATCH.
  - On success, shows a toast with title "Order cancelled" and an
    "Undo" ToastAction. Clicking Undo PATCHes the order status back to
    the previous value, then shows a "Restored" confirmation toast.
  - UNDO_WINDOW_MS = 7000 to match the owner-side flow.
- artifacts/api-server/src/routes/service-orders.ts
  - Refactored the PATCH /orders/:id/status authorization. Detects
    "restore from cancelled" up front (existing.status === cancelled
    and next !== cancelled) and grants permission to:
      - owner/admin: may restore to pending or received (existing rule)
      - original assignee: may restore to received or preparing (NEW)
  - The same 15s server-side undo window applies.
- artifacts/teaboy-os/src/locales/{en,ar}.json
  - Added incomingOrders.cancelled / undo / undone / restoreFailed
    strings in English and Arabic.
- Regenerated lib/api-zod and lib/api-client-react via the api-spec
  codegen script (the previously-cached output was missing pending /
  received / preparing from the request body enum, which now matches
  the OpenAPI spec).

Verification:
- Typecheck passes for api-server and teaboy-os.
- e2e flow verified: cancelling a received order shows the Undo toast;
  clicking Undo restores the order to "received" (DB confirmed);
  letting the window expire leaves the order permanently "cancelled"
  (DB confirmed).

Deviations: None.
2026-04-22 09:58:49 +00:00
riyadhafraa 7d29d63d2d Add Undo window when receivers cancel an incoming order
Original task (#73): Mirror the existing 7s Undo toast (already in place
for owner-side cancels in My Orders) on the receiver-side cancel action
in the Incoming Orders page.

Implementation:
- artifacts/teaboy-os/src/pages/orders-incoming.tsx
  - Captures the order's previous status (received | preparing) before
    issuing the cancel PATCH.
  - On success, shows a toast with title "Order cancelled" and an
    "Undo" ToastAction. Clicking Undo PATCHes the order status back to
    the previous value, then shows a "Restored" confirmation toast.
  - UNDO_WINDOW_MS = 7000 to match the owner-side flow.
- artifacts/api-server/src/routes/service-orders.ts
  - Refactored the PATCH /orders/:id/status authorization. Detects
    "restore from cancelled" up front (existing.status === cancelled
    and next !== cancelled) and grants permission to:
      - owner/admin: may restore to pending or received (existing rule)
      - original assignee: may restore to received or preparing (NEW)
  - The same 15s server-side undo window applies.
- artifacts/teaboy-os/src/locales/{en,ar}.json
  - Added incomingOrders.cancelled / undo / undone / restoreFailed
    strings in English and Arabic.
- Regenerated lib/api-zod and lib/api-client-react via the api-spec
  codegen script (the previously-cached output was missing pending /
  received / preparing from the request body enum, which now matches
  the OpenAPI spec).

Verification:
- Typecheck passes for api-server and teaboy-os.
- e2e flow verified: cancelling a received order shows the Undo toast;
  clicking Undo restores the order to "received" (DB confirmed);
  letting the window expire leaves the order permanently "cancelled"
  (DB confirmed).

Deviations: None.
Replit-Task-Id: 65c4b053-613a-4898-a0f7-b6fb2d680301
2026-04-22 09:58:49 +00:00
Riyadh 00933ac228 Git commit prior to merge 2026-04-22 09:58:49 +00:00