Commit Graph

2247 Commits

Author SHA1 Message Date
Riyadh 0d18077766 Adjust the large clock's default position to the far left in RTL view
Set the large clock's starting column to ensure it spans the last two columns in RTL, visually pinning it to the leftmost position.
2026-04-21 17:50:23 +00:00
riyadhafraa f5d50aa4e7 Adjust the large clock's default position to the far left in RTL view
Set the large clock's starting column to ensure it spans the last two columns in RTL, visually pinning it to the leftmost position.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: d13a5b95-5806-4d14-8588-81f7e67a4eac
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PwtbShH
Replit-Helium-Checkpoint-Created: true
2026-04-21 17:50:23 +00:00
Riyadh 396da9385a Set clock to the far left by default in Arabic mode
Modify the home page component to allow the clock tile to be pinned to the leftmost grid column in RTL layouts when its position is unset.
2026-04-21 17:45:06 +00:00
riyadhafraa 0fa79d6b3c Set clock to the far left by default in Arabic mode
Modify the home page component to allow the clock tile to be pinned to the leftmost grid column in RTL layouts when its position is unset.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1bb970e6-955c-4abc-a859-c78241c792f1
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PwtbShH
Replit-Helium-Checkpoint-Created: true
2026-04-21 17:45:06 +00:00
Riyadh 6c04ce6d63 Remove close button from clock widget and simplify its props
Remove the 'X' close button from the clock widget and refactor its props, simplifying the component by removing unused `onHide` and `hideLabel` functionalities.
2026-04-21 17:41:46 +00:00
riyadhafraa a4b9ad9b86 Remove close button from clock widget and simplify its props
Remove the 'X' close button from the clock widget and refactor its props, simplifying the component by removing unused `onHide` and `hideLabel` functionalities.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b2e77920-44ee-4103-9c61-e01b6ccb0094
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PwtbShH
Replit-Helium-Checkpoint-Created: true
2026-04-21 17:41:46 +00:00
Riyadh abb8a79c2c Hide scrollbars across the entire website while keeping scrolling functional
Add global CSS rules to `index.css` to hide scrollbars using `scrollbar-width: none`, `-ms-overflow-style: none`, and `::-webkit-scrollbar` properties, ensuring scrolling remains functional via mouse wheel or touch.
2026-04-21 17:39:31 +00:00
riyadhafraa 369e418fd3 Hide scrollbars across the entire website while keeping scrolling functional
Add global CSS rules to `index.css` to hide scrollbars using `scrollbar-width: none`, `-ms-overflow-style: none`, and `::-webkit-scrollbar` properties, ensuring scrolling remains functional via mouse wheel or touch.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a69cb334-1c57-4350-9daf-a639b8f19357
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PwtbShH
Replit-Helium-Checkpoint-Created: true
2026-04-21 17:39:31 +00:00
Riyadh 32221bdc43 Remove home greeting and date block (Task #60)
Per user request, the home page no longer shows the time-of-day greeting
("مساء الخير، {name}") nor the day-name + Gregorian date subtitle. The
apps grid (and the admin stats row when applicable) now sits directly
under the top bar.

Changes (artifacts/teaboy-os/src/pages/home.tsx):
- Removed the entire greeting block (the wrapper div, the <h1> greeting
  and the date <p>) from the Main Content section.
- Removed the now-unused `dayName`, `gregorianDate` and `greeting`
  locals plus the `formatWeekday` and `greetingKey` imports.
- Kept `displayName` (still used in the avatar header) and the
  `home.greeting.*` translation keys (left in place; cheap to keep and
  avoids touching unrelated locale files).

Mid-task fix:
- First pass also dropped `displayName` which broke the avatar
  (`displayName is not defined`). Restored it; `displayName` is needed
  by the user-card initials and name labels.

Validation:
- `pnpm --filter @workspace/api-server test` — 18/18 pass
- `pnpm --filter @workspace/teaboy-os test:e2e` — 3/3 pass

Notes:
- Pre-existing TypeScript errors in admin.tsx / clock-style-picker.tsx /
  clock.tsx are unrelated to this task (codegen drift from earlier
  tasks).
2026-04-21 17:38:36 +00:00
riyadhafraa 01fecdc93e Remove home greeting and date block (Task #60)
Per user request, the home page no longer shows the time-of-day greeting
("مساء الخير، {name}") nor the day-name + Gregorian date subtitle. The
apps grid (and the admin stats row when applicable) now sits directly
under the top bar.

Changes (artifacts/teaboy-os/src/pages/home.tsx):
- Removed the entire greeting block (the wrapper div, the <h1> greeting
  and the date <p>) from the Main Content section.
- Removed the now-unused `dayName`, `gregorianDate` and `greeting`
  locals plus the `formatWeekday` and `greetingKey` imports.
- Kept `displayName` (still used in the avatar header) and the
  `home.greeting.*` translation keys (left in place; cheap to keep and
  avoids touching unrelated locale files).

Mid-task fix:
- First pass also dropped `displayName` which broke the avatar
  (`displayName is not defined`). Restored it; `displayName` is needed
  by the user-card initials and name labels.

Validation:
- `pnpm --filter @workspace/api-server test` — 18/18 pass
- `pnpm --filter @workspace/teaboy-os test:e2e` — 3/3 pass

Notes:
- Pre-existing TypeScript errors in admin.tsx / clock-style-picker.tsx /
  clock.tsx are unrelated to this task (codegen drift from earlier
  tasks).
2026-04-21 17:38:36 +00:00
Riyadh 13b067166f Remove home greeting and date block (Task #60)
Per user request, the home page no longer shows the time-of-day greeting
("مساء الخير، {name}") nor the day-name + Gregorian date subtitle. The
apps grid (and the admin stats row when applicable) now sits directly
under the top bar.

Changes (artifacts/teaboy-os/src/pages/home.tsx):
- Removed the entire greeting block (the wrapper div, the <h1> greeting
  and the date <p>) from the Main Content section.
- Removed the now-unused `dayName`, `gregorianDate` and `greeting`
  locals plus the `formatWeekday` and `greetingKey` imports.
- Kept `displayName` (still used in the avatar header) and the
  `home.greeting.*` translation keys (left in place; cheap to keep and
  avoids touching unrelated locale files).

Mid-task fix:
- First pass also dropped `displayName` which broke the avatar
  (`displayName is not defined`). Restored it; `displayName` is needed
  by the user-card initials and name labels.

Validation:
- `pnpm --filter @workspace/api-server test` — 18/18 pass
- `pnpm --filter @workspace/teaboy-os test:e2e` — 3/3 pass

Notes:
- Pre-existing TypeScript errors in admin.tsx / clock-style-picker.tsx /
  clock.tsx are unrelated to this task (codegen drift from earlier
  tasks).
2026-04-21 17:37:46 +00:00
riyadhafraa 1ef055c245 Remove home greeting and date block (Task #60)
Per user request, the home page no longer shows the time-of-day greeting
("مساء الخير، {name}") nor the day-name + Gregorian date subtitle. The
apps grid (and the admin stats row when applicable) now sits directly
under the top bar.

Changes (artifacts/teaboy-os/src/pages/home.tsx):
- Removed the entire greeting block (the wrapper div, the <h1> greeting
  and the date <p>) from the Main Content section.
- Removed the now-unused `dayName`, `gregorianDate` and `greeting`
  locals plus the `formatWeekday` and `greetingKey` imports.
- Kept `displayName` (still used in the avatar header) and the
  `home.greeting.*` translation keys (left in place; cheap to keep and
  avoids touching unrelated locale files).

Mid-task fix:
- First pass also dropped `displayName` which broke the avatar
  (`displayName is not defined`). Restored it; `displayName` is needed
  by the user-card initials and name labels.

Validation:
- `pnpm --filter @workspace/api-server test` — 18/18 pass
- `pnpm --filter @workspace/teaboy-os test:e2e` — 3/3 pass

Notes:
- Pre-existing TypeScript errors in admin.tsx / clock-style-picker.tsx /
  clock.tsx are unrelated to this task (codegen drift from earlier
  tasks).
2026-04-21 17:37:46 +00:00
riyadhafraa eb7e79c719 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: e03eb2f4-4e17-4744-9bcc-786a6c9593dc
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/PwtbShH
Replit-Helium-Checkpoint-Created: true
2026-04-21 17:34:45 +00:00
Riyadh 49f4ab4dbb Revert clock-hidden hint and remove "My Apps" header (rebased)
Original task #58 was to add a dismissible hint when the home clock is
hidden. The hint was implemented and verified end-to-end (EN + AR/RTL),
but the user then asked to remove it. This commit removes the hint and
also removes the "تطبيقاتي · N" / "My Apps · N" header above the apps
grid per the user's request to show the apps directly.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - Removed the hidden-clock hint UI block, its session-storage state
    (clockHintDismissed) and the related useEffect/dismiss helper.
  - Removed the unused Clock-icon lucide import.
  - Removed the "My Apps" heading row (h3 + count) above the apps grid.
- artifacts/teaboy-os/src/locales/en.json, ar.json
  - Removed the new keys: home.clockStyle.hiddenHint,
    hiddenHintAction, dismissHint.

Rebase notes:
- Rebased onto main (a5a7a87). Two conflicts:
  * artifacts/teaboy-os/public/opengraph.jpg — binary asset unrelated
    to this task; took main's version (--ours during rebase).
  * artifacts/teaboy-os/src/pages/home.tsx — main re-styled the apps
    grid header (flex/gap layout with inline " · N" count) and added
    a useGridCols() hook. My commit removes that header entirely per
    user request, so kept the user-requested removal while preserving
    main's useGridCols() addition. No semantic divergence — just the
    deletion still applies cleanly to the new header markup.

Notes:
- Clock can still be hidden via the X on the clock tile and shown
  again via the clock-style popover in the top bar (existing behavior).
- Translation key home.myApps is still defined but no longer used on
  the home page; left in place to avoid touching other consumers.
- Pre-existing TypeScript errors on AuthUser.clockStyle/clockHour12
  in chat.tsx/home.tsx are unrelated to this task.
2026-04-21 17:32:28 +00:00
riyadhafraa 729f00d156 Revert clock-hidden hint and remove "My Apps" header (rebased)
Original task #58 was to add a dismissible hint when the home clock is
hidden. The hint was implemented and verified end-to-end (EN + AR/RTL),
but the user then asked to remove it. This commit removes the hint and
also removes the "تطبيقاتي · N" / "My Apps · N" header above the apps
grid per the user's request to show the apps directly.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - Removed the hidden-clock hint UI block, its session-storage state
    (clockHintDismissed) and the related useEffect/dismiss helper.
  - Removed the unused Clock-icon lucide import.
  - Removed the "My Apps" heading row (h3 + count) above the apps grid.
- artifacts/teaboy-os/src/locales/en.json, ar.json
  - Removed the new keys: home.clockStyle.hiddenHint,
    hiddenHintAction, dismissHint.

Rebase notes:
- Rebased onto main (a05249f). Two conflicts:
  * artifacts/teaboy-os/public/opengraph.jpg — binary asset unrelated
    to this task; took main's version (--ours during rebase).
  * artifacts/teaboy-os/src/pages/home.tsx — main re-styled the apps
    grid header (flex/gap layout with inline " · N" count) and added
    a useGridCols() hook. My commit removes that header entirely per
    user request, so kept the user-requested removal while preserving
    main's useGridCols() addition. No semantic divergence — just the
    deletion still applies cleanly to the new header markup.

Notes:
- Clock can still be hidden via the X on the clock tile and shown
  again via the clock-style popover in the top bar (existing behavior).
- Translation key home.myApps is still defined but no longer used on
  the home page; left in place to avoid touching other consumers.
- Pre-existing TypeScript errors on AuthUser.clockStyle/clockHour12
  in chat.tsx/home.tsx are unrelated to this task.

Replit-Task-Id: 14544719-933c-49f8-84a0-6577d06fbc14
2026-04-21 17:32:28 +00:00
riyadhafraa 7b5113e3d5 Git commit prior to merge 2026-04-21 17:32:27 +00:00
Riyadh a5a7a8768e Task #59: Clean home clock tile and tighten My Apps header.
Original ask (Arabic):
- Why does a small "4" appear on the left of the home page?
- Make the clock movable to all positions, default to the visual-left,
  remove its frame, and keep it parallel/aligned with the app icons.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - My Apps header: replaced `justify-between` with `gap-2`, prefixed
    the count with "·" so it now reads "تطبيقاتي · 5" / "MY APPS · 5"
    inline with the title. The standalone count badge that floated to
    the opposite edge in RTL is gone.
  - SortableClockTile inner box: removed the `bg-white/85
    dark:bg-white/10 backdrop-blur-md` and the `icon-tile` rounded card
    classes. The analog clock face now renders directly on the
    wallpaper. Outer width/height (78px compact, 168px large) and the
    hover/active scale animation are preserved so grid alignment with
    the app icons is unchanged.
  - Default clock position is now language-aware: when no per-user
    saved position exists, the clock defaults to the visually-leftmost
    cell of the first row — last DOM index in RTL (Arabic),
    first DOM index in LTR (English).

- artifacts/teaboy-os/src/components/clock.tsx
  - `readHomeClockPosition` now returns -1 as the "unset" sentinel
    instead of 0, so home.tsx can apply a language-aware default
    without overwriting saved positions.

Verification:
- Manual e2e: confirmed header has the inline count, clock has no
  frame, RTL default position is on the visual-left, drag-and-drop
  still persists across reload, long-press still toggles compact ↔
  large. All passed.

Out of scope: no server-side persistence; no changes to the topbar
clock, the dock, or the clock-style picker.
2026-04-21 13:41:47 +00:00
riyadhafraa a05249fb96 Task #59: Clean home clock tile and tighten My Apps header.
Original ask (Arabic):
- Why does a small "4" appear on the left of the home page?
- Make the clock movable to all positions, default to the visual-left,
  remove its frame, and keep it parallel/aligned with the app icons.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - My Apps header: replaced `justify-between` with `gap-2`, prefixed
    the count with "·" so it now reads "تطبيقاتي · 5" / "MY APPS · 5"
    inline with the title. The standalone count badge that floated to
    the opposite edge in RTL is gone.
  - SortableClockTile inner box: removed the `bg-white/85
    dark:bg-white/10 backdrop-blur-md` and the `icon-tile` rounded card
    classes. The analog clock face now renders directly on the
    wallpaper. Outer width/height (78px compact, 168px large) and the
    hover/active scale animation are preserved so grid alignment with
    the app icons is unchanged.
  - Default clock position is now language-aware: when no per-user
    saved position exists, the clock defaults to the visually-leftmost
    cell of the first row — last DOM index in RTL (Arabic),
    first DOM index in LTR (English).

- artifacts/teaboy-os/src/components/clock.tsx
  - `readHomeClockPosition` now returns -1 as the "unset" sentinel
    instead of 0, so home.tsx can apply a language-aware default
    without overwriting saved positions.

Verification:
- Manual e2e: confirmed header has the inline count, clock has no
  frame, RTL default position is on the visual-left, drag-and-drop
  still persists across reload, long-press still toggles compact ↔
  large. All passed.

Out of scope: no server-side persistence; no changes to the topbar
clock, the dock, or the clock-style picker.
2026-04-21 13:41:47 +00:00
Riyadh bb0fd4e350 Task #59: Clean home clock tile and tighten My Apps header.
Original ask (Arabic):
- Why does a small "4" appear on the left of the home page?
- Make the clock movable to all positions, default to the visual-left,
  remove its frame, and keep it parallel/aligned with the app icons.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - My Apps header: replaced `justify-between` with `gap-2`, prefixed
    the count with "·" so it now reads "تطبيقاتي · 5" / "MY APPS · 5"
    inline with the title. The standalone count badge that floated to
    the opposite edge in RTL is gone.
  - SortableClockTile inner box: removed the `bg-white/85
    dark:bg-white/10 backdrop-blur-md` and the `icon-tile` rounded card
    classes. The analog clock face now renders directly on the
    wallpaper. Outer width/height (78px compact, 168px large) and the
    hover/active scale animation are preserved so grid alignment with
    the app icons is unchanged.
  - Default clock position is now language-aware: when no per-user
    saved position exists, the clock defaults to the visually-leftmost
    cell of the first row — last DOM index in RTL (Arabic),
    first DOM index in LTR (English).

- artifacts/teaboy-os/src/components/clock.tsx
  - `readHomeClockPosition` now returns -1 as the "unset" sentinel
    instead of 0, so home.tsx can apply a language-aware default
    without overwriting saved positions.

Verification:
- Manual e2e: confirmed header has the inline count, clock has no
  frame, RTL default position is on the visual-left, drag-and-drop
  still persists across reload, long-press still toggles compact ↔
  large. All passed.

Out of scope: no server-side persistence; no changes to the topbar
clock, the dock, or the clock-style picker.
2026-04-21 13:40:51 +00:00
riyadhafraa d9a4c1ba34 Task #59: Clean home clock tile and tighten My Apps header.
Original ask (Arabic):
- Why does a small "4" appear on the left of the home page?
- Make the clock movable to all positions, default to the visual-left,
  remove its frame, and keep it parallel/aligned with the app icons.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - My Apps header: replaced `justify-between` with `gap-2`, prefixed
    the count with "·" so it now reads "تطبيقاتي · 5" / "MY APPS · 5"
    inline with the title. The standalone count badge that floated to
    the opposite edge in RTL is gone.
  - SortableClockTile inner box: removed the `bg-white/85
    dark:bg-white/10 backdrop-blur-md` and the `icon-tile` rounded card
    classes. The analog clock face now renders directly on the
    wallpaper. Outer width/height (78px compact, 168px large) and the
    hover/active scale animation are preserved so grid alignment with
    the app icons is unchanged.
  - Default clock position is now language-aware: when no per-user
    saved position exists, the clock defaults to the visually-leftmost
    cell of the first row — last DOM index in RTL (Arabic),
    first DOM index in LTR (English).

- artifacts/teaboy-os/src/components/clock.tsx
  - `readHomeClockPosition` now returns -1 as the "unset" sentinel
    instead of 0, so home.tsx can apply a language-aware default
    without overwriting saved positions.

Verification:
- Manual e2e: confirmed header has the inline count, clock has no
  frame, RTL default position is on the visual-left, drag-and-drop
  still persists across reload, long-press still toggles compact ↔
  large. All passed.

Out of scope: no server-side persistence; no changes to the topbar
clock, the dock, or the clock-style picker.
2026-04-21 13:40:51 +00:00
Riyadh 4e2c21016b Task #59: Clean home clock tile and tighten My Apps header.
Original ask (Arabic):
- Why does a small "4" appear on the left of the home page?
- Make the clock movable to all positions, default to the visual-left,
  remove its frame, and keep it parallel/aligned with the app icons.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - My Apps header: replaced `justify-between` with `gap-2`, prefixed
    the count with "·" so it now reads "تطبيقاتي · 5" / "MY APPS · 5"
    inline with the title. The standalone count badge that floated to
    the opposite edge in RTL is gone.
  - SortableClockTile inner box: removed the `bg-white/85
    dark:bg-white/10 backdrop-blur-md` and the `icon-tile` rounded card
    classes. The analog clock face now renders directly on the
    wallpaper. Outer width/height (78px compact, 168px large) and the
    hover/active scale animation are preserved so grid alignment with
    the app icons is unchanged.
  - Default clock position is now language-aware: when no per-user
    saved position exists, the clock defaults to the visually-leftmost
    cell of the first row — last DOM index in RTL (Arabic),
    first DOM index in LTR (English).

- artifacts/teaboy-os/src/components/clock.tsx
  - `readHomeClockPosition` now returns -1 as the "unset" sentinel
    instead of 0, so home.tsx can apply a language-aware default
    without overwriting saved positions.

Verification:
- Manual e2e: confirmed header has the inline count, clock has no
  frame, RTL default position is on the visual-left, drag-and-drop
  still persists across reload, long-press still toggles compact ↔
  large. All passed.

Out of scope: no server-side persistence; no changes to the topbar
clock, the dock, or the clock-style picker.
2026-04-21 13:39:22 +00:00
riyadhafraa 518d3267b6 Task #59: Clean home clock tile and tighten My Apps header.
Original ask (Arabic):
- Why does a small "4" appear on the left of the home page?
- Make the clock movable to all positions, default to the visual-left,
  remove its frame, and keep it parallel/aligned with the app icons.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx
  - My Apps header: replaced `justify-between` with `gap-2`, prefixed
    the count with "·" so it now reads "تطبيقاتي · 5" / "MY APPS · 5"
    inline with the title. The standalone count badge that floated to
    the opposite edge in RTL is gone.
  - SortableClockTile inner box: removed the `bg-white/85
    dark:bg-white/10 backdrop-blur-md` and the `icon-tile` rounded card
    classes. The analog clock face now renders directly on the
    wallpaper. Outer width/height (78px compact, 168px large) and the
    hover/active scale animation are preserved so grid alignment with
    the app icons is unchanged.
  - Default clock position is now language-aware: when no per-user
    saved position exists, the clock defaults to the visually-leftmost
    cell of the first row — last DOM index in RTL (Arabic),
    first DOM index in LTR (English).

- artifacts/teaboy-os/src/components/clock.tsx
  - `readHomeClockPosition` now returns -1 as the "unset" sentinel
    instead of 0, so home.tsx can apply a language-aware default
    without overwriting saved positions.

Verification:
- Manual e2e: confirmed header has the inline count, clock has no
  frame, RTL default position is on the visual-left, drag-and-drop
  still persists across reload, long-press still toggles compact ↔
  large. All passed.

Out of scope: no server-side persistence; no changes to the topbar
clock, the dock, or the clock-style picker.
2026-04-21 13:39:22 +00:00
riyadhafraa 6b81d6de25 Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4702309f-1078-45a3-bee6-f48d2d72967f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/WDiwoqy
Replit-Helium-Checkpoint-Created: true
2026-04-21 13:35:00 +00:00
Riyadh 1c6220e710 Add e2e test for home clock position and size persistence
Task #57: add an automated check that exercises the same flow that was
previously only manually verified — drag the home-page clock tile to a new
grid slot, long-press to flip it from compact to large, reload, and assert
both the position and size survive.

Implementation
- New Playwright spec at
  artifacts/teaboy-os/tests/home-clock-persistence.spec.mjs, mirroring the
  conventions of the existing leave-group-successor.spec.mjs (DB-seeded
  user, UI login, cleanup in afterAll).
- Drives a real pointer drag that satisfies dnd-kit's 8px activation
  threshold, then a separate long-press (>500ms, no movement) to trigger
  the size toggle in home.tsx without aborting the timer.
- Asserts both per-user localStorage keys are written
  (teaboy:home-clock-position:<id> and teaboy:home-clock-size:<id>) and
  that, after page.reload(), the clock tile still has col-span-2 / row-span-2
  classes and lives at the persisted index in the grid (not just in
  storage).

Verification
- Ran the new spec in isolation: 1 passed.
- Ran the full validation workflow (api-server tests + teaboy-os
  test:e2e): 18 + 3 passed.

No production code changes.
2026-04-21 13:22:37 +00:00
riyadhafraa 967cd628fa Add e2e test for home clock position and size persistence
Task #57: add an automated check that exercises the same flow that was
previously only manually verified — drag the home-page clock tile to a new
grid slot, long-press to flip it from compact to large, reload, and assert
both the position and size survive.

Implementation
- New Playwright spec at
  artifacts/teaboy-os/tests/home-clock-persistence.spec.mjs, mirroring the
  conventions of the existing leave-group-successor.spec.mjs (DB-seeded
  user, UI login, cleanup in afterAll).
- Drives a real pointer drag that satisfies dnd-kit's 8px activation
  threshold, then a separate long-press (>500ms, no movement) to trigger
  the size toggle in home.tsx without aborting the timer.
- Asserts both per-user localStorage keys are written
  (teaboy:home-clock-position:<id> and teaboy:home-clock-size:<id>) and
  that, after page.reload(), the clock tile still has col-span-2 / row-span-2
  classes and lives at the persisted index in the grid (not just in
  storage).

Verification
- Ran the new spec in isolation: 1 passed.
- Ran the full validation workflow (api-server tests + teaboy-os
  test:e2e): 18 + 3 passed.

No production code changes.

Replit-Task-Id: bd8d8372-358c-4676-b842-956dc0813cb8
2026-04-21 13:22:37 +00:00
Riyadh 27531a3550 Add one-tap hide control to home clock tile
Original task: #56 — Let users hide or show the home clock with one tap.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx: Added a small dismiss "×"
  button overlay on the SortableClockTile. The button stops pointer
  propagation so it does not trigger drag or the existing long-press
  size toggle. It calls setHomeClockVisible(false) (now destructured
  from useHomeClockVisibility) to hide the tile in one tap. The button
  uses absolute positioning with `end-1` so it works in both LTR and
  RTL. It is always visible at large size and revealed on hover/focus
  at compact size to keep the tile clean.
- artifacts/teaboy-os/src/locales/{en,ar}.json: Added
  `home.clockStyle.hideWidget` ("Hide clock" / "إخفاء الساعة") used
  as the button's aria-label and tooltip.

Re-showing the clock is handled by the existing toggle in the clock
style picker popover (already bilingual), so users can bring the
clock back later. Hiding/showing keeps the persisted grid position.

Notes / deviations:
- Did not add a context-menu since long-press is already used for the
  size toggle on the same tile and would conflict.
- Pre-existing TypeScript errors in unrelated files (admin.tsx,
  clock-style-picker.tsx codegen drift) are not addressed here.
2026-04-21 13:16:15 +00:00
riyadhafraa f56cd425c8 Add one-tap hide control to home clock tile
Original task: #56 — Let users hide or show the home clock with one tap.

Changes:
- artifacts/teaboy-os/src/pages/home.tsx: Added a small dismiss "×"
  button overlay on the SortableClockTile. The button stops pointer
  propagation so it does not trigger drag or the existing long-press
  size toggle. It calls setHomeClockVisible(false) (now destructured
  from useHomeClockVisibility) to hide the tile in one tap. The button
  uses absolute positioning with `end-1` so it works in both LTR and
  RTL. It is always visible at large size and revealed on hover/focus
  at compact size to keep the tile clean.
- artifacts/teaboy-os/src/locales/{en,ar}.json: Added
  `home.clockStyle.hideWidget` ("Hide clock" / "إخفاء الساعة") used
  as the button's aria-label and tooltip.

Re-showing the clock is handled by the existing toggle in the clock
style picker popover (already bilingual), so users can bring the
clock back later. Hiding/showing keeps the persisted grid position.

Notes / deviations:
- Did not add a context-menu since long-press is already used for the
  size toggle on the same tile and would conflict.
- Pre-existing TypeScript errors in unrelated files (admin.tsx,
  clock-style-picker.tsx codegen drift) are not addressed here.

Replit-Task-Id: 87bb057d-dc81-4916-8bb1-b24e34ff4794
2026-04-21 13:16:15 +00:00
Riyadh f1237d366c Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 13:04:45 +00:00
riyadhafraa 2eddc6d407 Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 13:04:45 +00:00
Riyadh b225ea8e46 Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 13:03:05 +00:00
riyadhafraa 5390a5a315 Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 13:03:05 +00:00
Riyadh 3734d1e99f Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 12:56:33 +00:00
riyadhafraa f52701ef74 Task #55: Movable, resizable home clock with Latin digits
- Refactor AnalogClockWidget: Latin 1-12 digits (was Roman), `size`
  (px) prop with scale-based interior, optional `showLabels`.
- Add useHomeClockSize (compact|large) and useHomeClockPosition hooks
  in clock.tsx — both persist to localStorage and broadcast a custom
  event so consumers stay in sync within the tab.
- home.tsx: introduce SortableClockTile that participates in the same
  dnd-kit grid as app icons via id `__clock`. App ids now prefixed
  `app-` so they don't collide. Long-press (500ms) toggles
  compact↔large; large = col-span-2 row-span-2. Custom pointer
  handlers chain with dnd-kit's listener so drag still works.
- handleDragEnd computes new app order excluding the clock and only
  fires updateOrder when app order actually changed.
- Remove the standalone AnalogClockWidget block above the apps grid;
  clock now lives inside the grid.
- When homeClockVisible is false, the clock is also excluded from
  sortable items (no dnd-kit warnings).
- e2e test passed: in-grid placement, Latin digits, long-press
  resize, drag-reorder, position persists across reload.
2026-04-21 12:56:33 +00:00
riyadhafraa 5f88e00ddf Transitioned from Plan to Build mode
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: c0086521-bd82-4df9-a2d1-6abe4a435836
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
2026-04-21 12:45:31 +00:00
Riyadh f32145d3e7 Task #54: Make staying signed in reliable across all sign-in points
Background:
Task #53 fixed the login/register session persistence race by explicitly
awaiting `req.session.save` before responding. Other session-mutating
endpoints (notably `/auth/logout`) still relied on express-session's
default end-hook, which can flush the response before the store write
finishes — producing intermittent "still logged in" / "logged out"
glitches on the immediate next request.

Changes:
- New shared helper `artifacts/api-server/src/lib/session.ts` exporting
  `saveSession(req)` and `destroySession(req)` — promise wrappers around
  `req.session.save` / `req.session.destroy` so handlers can `await`
  store persistence before flushing the HTTP response. Documented the
  rationale in the file so future session-mutating routes use the same
  safe pattern.
- `artifacts/api-server/src/routes/auth.ts`:
  - `/auth/register` and `/auth/login` now use `await saveSession(req)`
    in place of the inline ad-hoc Promise wrapper.
  - `/auth/logout` is now async and `await`s `destroySession(req)`
    before responding, closing the same race for the destroy path.
- Audited remaining routes: only `auth.ts` mutates `req.session`; all
  other handlers only read `req.session.userId`, so no further changes
  are needed.

Notes / deviations:
- Pre-existing TypeScript errors in unrelated files (conversations,
  notes, users, api-zod exports) were left untouched — out of scope.
- New test file `artifacts/api-server/tests/auth-session-persistence.test.mjs`
  covers the acceptance criterion: login / register / logout each
  followed by an immediate `/auth/me` probe to assert the session was
  persisted (or destroyed) before the response was flushed. Modeled on
  the existing leave-test pattern. Full suite: 18/18 passing.
2026-04-21 12:37:16 +00:00
riyadhafraa 1277c71d11 Task #54: Make staying signed in reliable across all sign-in points
Background:
Task #53 fixed the login/register session persistence race by explicitly
awaiting `req.session.save` before responding. Other session-mutating
endpoints (notably `/auth/logout`) still relied on express-session's
default end-hook, which can flush the response before the store write
finishes — producing intermittent "still logged in" / "logged out"
glitches on the immediate next request.

Changes:
- New shared helper `artifacts/api-server/src/lib/session.ts` exporting
  `saveSession(req)` and `destroySession(req)` — promise wrappers around
  `req.session.save` / `req.session.destroy` so handlers can `await`
  store persistence before flushing the HTTP response. Documented the
  rationale in the file so future session-mutating routes use the same
  safe pattern.
- `artifacts/api-server/src/routes/auth.ts`:
  - `/auth/register` and `/auth/login` now use `await saveSession(req)`
    in place of the inline ad-hoc Promise wrapper.
  - `/auth/logout` is now async and `await`s `destroySession(req)`
    before responding, closing the same race for the destroy path.
- Audited remaining routes: only `auth.ts` mutates `req.session`; all
  other handlers only read `req.session.userId`, so no further changes
  are needed.

Notes / deviations:
- Pre-existing TypeScript errors in unrelated files (conversations,
  notes, users, api-zod exports) were left untouched — out of scope.
- New test file `artifacts/api-server/tests/auth-session-persistence.test.mjs`
  covers the acceptance criterion: login / register / logout each
  followed by an immediate `/auth/me` probe to assert the session was
  persisted (or destroyed) before the response was flushed. Modeled on
  the existing leave-test pattern. Full suite: 18/18 passing.

Replit-Task-Id: 11b72d21-d7c2-42cb-a4d9-f1197cfad4c5
2026-04-21 12:37:16 +00:00
Riyadh 85cbcbf898 Fix flaky 'invalid successor' leave test: persist session before responding from login/register
Original task: investigate why "sole admin leaving with an invalid
successor returns 400 and does not leave" intermittently returned 401
instead of 400 in the API test suite.

Root cause:
express-session 1.19.0 wraps res.end and calls req.session.save()
asynchronously. Its writetop() helper synchronously flushes headers
(including Set-Cookie) and writes the body chunk before save completes,
only deferring the final _end until after the store write. With
Content-Length set, fetch sees the full body and resolves immediately,
so the test's next request (POST /conversations/:id/leave) frequently
arrived before connect-pg-simple finished inserting the session row in
Postgres. requireAuth then read no session, express-session generated a
brand-new sid, and the request was rejected with 401.

This was reproduced ~50% of the time across 10 runs and confirmed via
server-side logging showing the cookie sid not matching the freshly
generated server sid (proving store.get returned nothing).

Fix:
Explicitly await req.session.save() in the /auth/login and
/auth/register handlers after assigning req.session.userId. This
guarantees the session is persisted in Postgres before the response is
sent, eliminating the race for any client that immediately makes a
follow-up authenticated request.

Verification:
Ran the API test suite 15 consecutive times after the fix; all 15 runs
pass cleanly (15/15 tests). The full validation workflow also passes
the api-server tests on the post-fix run.

Files changed:
- artifacts/api-server/src/routes/auth.ts (await session.save in
  /auth/login and /auth/register)
- artifacts/api-server/src/middlewares/auth.ts (no behavior change;
  diagnostic logging added during debug was removed)
2026-04-21 12:33:24 +00:00
riyadhafraa e8245dfa35 Fix flaky 'invalid successor' leave test: persist session before responding from login/register
Original task: investigate why "sole admin leaving with an invalid
successor returns 400 and does not leave" intermittently returned 401
instead of 400 in the API test suite.

Root cause:
express-session 1.19.0 wraps res.end and calls req.session.save()
asynchronously. Its writetop() helper synchronously flushes headers
(including Set-Cookie) and writes the body chunk before save completes,
only deferring the final _end until after the store write. With
Content-Length set, fetch sees the full body and resolves immediately,
so the test's next request (POST /conversations/:id/leave) frequently
arrived before connect-pg-simple finished inserting the session row in
Postgres. requireAuth then read no session, express-session generated a
brand-new sid, and the request was rejected with 401.

This was reproduced ~50% of the time across 10 runs and confirmed via
server-side logging showing the cookie sid not matching the freshly
generated server sid (proving store.get returned nothing).

Fix:
Explicitly await req.session.save() in the /auth/login and
/auth/register handlers after assigning req.session.userId. This
guarantees the session is persisted in Postgres before the response is
sent, eliminating the race for any client that immediately makes a
follow-up authenticated request.

Verification:
Ran the API test suite 15 consecutive times after the fix; all 15 runs
pass cleanly (15/15 tests). The full validation workflow also passes
the api-server tests on the post-fix run.

Files changed:
- artifacts/api-server/src/routes/auth.ts (await session.save in
  /auth/login and /auth/register)
- artifacts/api-server/src/middlewares/auth.ts (no behavior change;
  diagnostic logging added during debug was removed)

Replit-Task-Id: 34cbe0e0-1d23-4257-962a-7e3adddba45c
2026-04-21 12:33:24 +00:00
Riyadh 54f847feab Wire leave-group successor picker UI test into automated suite
Original task: make the ad-hoc browser test for the leave-group
successor chooser dialog a persistent automated test that runs
alongside the existing api-server backend tests.

Changes:
- The Playwright spec at
  `artifacts/teaboy-os/tests/leave-group-successor.spec.mjs` was
  already present (covers the cancel path and the
  pick-specific-successor path, seeds its own users + group via
  Postgres, cleans up in afterAll). Verified it passes against the
  running web + api workflows.
- Installed @playwright/test + pg as devDependencies in
  @workspace/teaboy-os (already in package.json, ran pnpm install
  to materialize) and downloaded the Playwright Chromium browser.
- Added a root `test` script in package.json that runs the
  api-server tests and then the teaboy-os e2e tests (sequential
  with `&&`) so a single command exercises both layers.
- Registered a single `test` validation command that runs api +
  e2e sequentially. Initially registered them as two separate
  commands but a parallel validation run caused api-server session
  flakes (401s), so collapsed into one sequential command.
- Updated `scripts/post-merge.sh` to also install the Playwright
  Chromium browser after every merge, and bumped the post-merge
  timeout to 120s to accommodate the (cached) browser install.

Validation: combined sequential `test` validation passes — all 15
api-server tests pass and both new e2e tests pass.

Note: `artifacts/teaboy-os/public/opengraph.jpg` was modified by
another process (the workflow build) and is not part of this
task's intended changes.
2026-04-21 12:24:25 +00:00
riyadhafraa a69323986c Wire leave-group successor picker UI test into automated suite
Original task: make the ad-hoc browser test for the leave-group
successor chooser dialog a persistent automated test that runs
alongside the existing api-server backend tests.

Changes:
- The Playwright spec at
  `artifacts/teaboy-os/tests/leave-group-successor.spec.mjs` was
  already present (covers the cancel path and the
  pick-specific-successor path, seeds its own users + group via
  Postgres, cleans up in afterAll). Verified it passes against the
  running web + api workflows.
- Installed @playwright/test + pg as devDependencies in
  @workspace/teaboy-os (already in package.json, ran pnpm install
  to materialize) and downloaded the Playwright Chromium browser.
- Added a root `test` script in package.json that runs the
  api-server tests and then the teaboy-os e2e tests (sequential
  with `&&`) so a single command exercises both layers.
- Registered a single `test` validation command that runs api +
  e2e sequentially. Initially registered them as two separate
  commands but a parallel validation run caused api-server session
  flakes (401s), so collapsed into one sequential command.
- Updated `scripts/post-merge.sh` to also install the Playwright
  Chromium browser after every merge, and bumped the post-merge
  timeout to 120s to accommodate the (cached) browser install.

Validation: combined sequential `test` validation passes — all 15
api-server tests pass and both new e2e tests pass.

Note: `artifacts/teaboy-os/public/opengraph.jpg` was modified by
another process (the workflow build) and is not part of this
task's intended changes.

Replit-Task-Id: ab9b6d6f-b68b-47ea-94e5-c34352afeb64
2026-04-21 12:24:25 +00:00
Riyadh 3264122a45 Add a Playwright UI test for the leave-group successor picker
Original task: Add a UI test for the leave-group successor chooser dialog
in artifacts/teaboy-os/src/pages/chat.tsx, mirroring the backend coverage
in artifacts/api-server/tests/conversations-leave.test.mjs.

What I added:
- artifacts/teaboy-os/tests/leave-group-successor.spec.mjs
  A real Playwright e2e test that drives the chat UI in a browser:
  * Cancel path: opens the leave dialog as the sole admin, verifies the
    successor chooser is visible with the auto option pre-selected and
    one option per remaining member, clicks Cancel, and asserts via DB
    that the conversation membership and admin flags are unchanged.
  * Chosen-successor path: re-opens the dialog, picks the *later-joined*
    member (so the choice differs from the auto pick), confirms the
    leave, asserts the POST /conversations/:id/leave call returns 200,
    and verifies via DB that the leaver was removed and the explicitly
    chosen member became the new admin.
  Both tests seed their own users + group via SQL and clean up after
  themselves so they can run repeatedly against the live dev DB.

- artifacts/teaboy-os/playwright.config.mjs
  Minimal Playwright config: testDir=tests, *.spec.mjs match, headless,
  baseURL defaults to http://localhost:80 (the workspace proxy), and
  is overridable via TEST_WEB_BASE.

- artifacts/teaboy-os/package.json
  Added @playwright/test and pg as devDependencies and a `test:e2e`
  script: `playwright test --config playwright.config.mjs`.

- .gitignore
  Ignored Playwright's test-results/ and playwright-report/ output dirs.

How to run:
  pnpm --filter @workspace/teaboy-os exec playwright install chromium
  DATABASE_URL=... pnpm --filter @workspace/teaboy-os run test:e2e

Verification:
  Both tests pass locally (2 passed in ~9s) against the running dev
  workspace. Required system libraries for headless Chromium were
  installed via the workspace's system-deps mechanism (glib, nss, nspr,
  atk, cups, dbus, libdrm, libxkbcommon, libgbm, alsa-lib, pango, cairo,
  and the relevant xorg libs), so `playwright test` works out of the
  box on this environment.

No application source files were modified.
2026-04-21 12:17:20 +00:00
riyadhafraa cdc32061cb Add a Playwright UI test for the leave-group successor picker
Original task: Add a UI test for the leave-group successor chooser dialog
in artifacts/teaboy-os/src/pages/chat.tsx, mirroring the backend coverage
in artifacts/api-server/tests/conversations-leave.test.mjs.

What I added:
- artifacts/teaboy-os/tests/leave-group-successor.spec.mjs
  A real Playwright e2e test that drives the chat UI in a browser:
  * Cancel path: opens the leave dialog as the sole admin, verifies the
    successor chooser is visible with the auto option pre-selected and
    one option per remaining member, clicks Cancel, and asserts via DB
    that the conversation membership and admin flags are unchanged.
  * Chosen-successor path: re-opens the dialog, picks the *later-joined*
    member (so the choice differs from the auto pick), confirms the
    leave, asserts the POST /conversations/:id/leave call returns 200,
    and verifies via DB that the leaver was removed and the explicitly
    chosen member became the new admin.
  Both tests seed their own users + group via SQL and clean up after
  themselves so they can run repeatedly against the live dev DB.

- artifacts/teaboy-os/playwright.config.mjs
  Minimal Playwright config: testDir=tests, *.spec.mjs match, headless,
  baseURL defaults to http://localhost:80 (the workspace proxy), and
  is overridable via TEST_WEB_BASE.

- artifacts/teaboy-os/package.json
  Added @playwright/test and pg as devDependencies and a `test:e2e`
  script: `playwright test --config playwright.config.mjs`.

- .gitignore
  Ignored Playwright's test-results/ and playwright-report/ output dirs.

How to run:
  pnpm --filter @workspace/teaboy-os exec playwright install chromium
  DATABASE_URL=... pnpm --filter @workspace/teaboy-os run test:e2e

Verification:
  Both tests pass locally (2 passed in ~9s) against the running dev
  workspace. Required system libraries for headless Chromium were
  installed via the workspace's system-deps mechanism (glib, nss, nspr,
  atk, cups, dbus, libdrm, libxkbcommon, libgbm, alsa-lib, pango, cairo,
  and the relevant xorg libs), so `playwright test` works out of the
  box on this environment.

No application source files were modified.

Replit-Task-Id: d2f21eab-498e-4cc0-a913-6035b714b3da
2026-04-21 12:17:20 +00:00
Riyadh fbfe2c7ef5 Git commit prior to merge 2026-04-21 12:16:59 +00:00
riyadhafraa 1055866105 Git commit prior to merge 2026-04-21 12:16:59 +00:00
Riyadh f311871ec8 Add automated tests for the leave-and-handoff flow (Task #50)
Adds artifacts/api-server/tests/conversations-leave.test.mjs, modeled on
the existing apps-open.test.mjs, covering POST /conversations/:id/leave:

- Solo member leaving deletes the conversation entirely.
- Sole admin leaving with no successor auto-promotes the
  earliest-joined remaining member.
- Sole admin leaving with a chosen successorId promotes that user.
- Sole admin leaving with a non-member successorId returns 400 and
  leaves the group untouched (leaver still admin, no promotion).
- Non-admin leaving a group removes them with no admin promotion.

Tests create their own users (with the standard user role) and groups
directly in Postgres so joined_at ordering is deterministic for the
auto-promotion case, then exercise the route through HTTP using a real
session cookie obtained from POST /api/auth/login. An after() hook
cleans up all created conversations, participants, messages, role
assignments, and users.

The optional e2e for the chooser dialog is intentionally deferred and
proposed as follow-up #51.

Verified by running `pnpm --filter @workspace/api-server test` three
times consecutively; all 15 tests pass on every run.
2026-04-21 12:01:41 +00:00
riyadhafraa 8fb6d54d78 Add automated tests for the leave-and-handoff flow (Task #50)
Adds artifacts/api-server/tests/conversations-leave.test.mjs, modeled on
the existing apps-open.test.mjs, covering POST /conversations/:id/leave:

- Solo member leaving deletes the conversation entirely.
- Sole admin leaving with no successor auto-promotes the
  earliest-joined remaining member.
- Sole admin leaving with a chosen successorId promotes that user.
- Sole admin leaving with a non-member successorId returns 400 and
  leaves the group untouched (leaver still admin, no promotion).
- Non-admin leaving a group removes them with no admin promotion.

Tests create their own users (with the standard user role) and groups
directly in Postgres so joined_at ordering is deterministic for the
auto-promotion case, then exercise the route through HTTP using a real
session cookie obtained from POST /api/auth/login. An after() hook
cleans up all created conversations, participants, messages, role
assignments, and users.

The optional e2e for the chooser dialog is intentionally deferred and
proposed as follow-up #51.

Verified by running `pnpm --filter @workspace/api-server test` three
times consecutively; all 15 tests pass on every run.

Replit-Task-Id: e31c169d-a4f5-4387-a642-b39a422c1408
2026-04-21 12:01:41 +00:00
Riyadh 84b34019e7 Show new admin clearly after sole admin leaves group chat
Task #49: When the sole admin leaves a group and a successor is auto- or
manually-promoted, surface the change prominently in addition to the existing
system messages.

Changes:
- artifacts/teaboy-os/src/pages/chat.tsx
  - New effect on conversations list: tracks the set of group conversations
    where the current user is currently an admin (from participant.isAdmin).
    On first load it silently records a baseline in localStorage
    (key teaboy:admin-known:<userId>); thereafter, any newly-admin group
    triggers a toast ("You are now the admin of <group>"). The set is pruned
    when the user is no longer admin so a future re-promotion toasts again.
    Using participant.isAdmin (rather than only lastMessage) ensures the cue
    fires even if newer messages have arrived in the conversation since the
    promotion.
  - Conversation list rows now render a small amber "New admin" / "You are now
    the admin" Crown badge whenever the most recent message is an
    admin_promoted system message. Variant differs for the promoted user vs
    other members. data-testid="badge-new-admin-<convId>" for tests.
  - Imported Crown from lucide-react.
- artifacts/teaboy-os/src/locales/en.json + ar.json
  - Added bilingual strings under chat.actions:
    youAreAdminTitle, youAreAdminDescription, newAdminBadge, newAdminLabel.

Implementation notes:
- lastMessage.meta is already returned by GET /conversations and includes the
  promoted user (id, displayNameAr/En, username), so no API changes were needed.
- Regenerated api-client-react via `pnpm --filter @workspace/api-spec run
  codegen` (pre-existing stale codegen was failing typecheck unrelated to this
  task, now clean for chat.tsx).
- Did not add automated tests; the existing follow-up task "Make sure the
  leave-and-handoff flow stays working with automated tests" already covers it.
2026-04-21 11:53:59 +00:00
riyadhafraa 42c2910d4b Show new admin clearly after sole admin leaves group chat
Task #49: When the sole admin leaves a group and a successor is auto- or
manually-promoted, surface the change prominently in addition to the existing
system messages.

Changes:
- artifacts/teaboy-os/src/pages/chat.tsx
  - New effect on conversations list: tracks the set of group conversations
    where the current user is currently an admin (from participant.isAdmin).
    On first load it silently records a baseline in localStorage
    (key teaboy:admin-known:<userId>); thereafter, any newly-admin group
    triggers a toast ("You are now the admin of <group>"). The set is pruned
    when the user is no longer admin so a future re-promotion toasts again.
    Using participant.isAdmin (rather than only lastMessage) ensures the cue
    fires even if newer messages have arrived in the conversation since the
    promotion.
  - Conversation list rows now render a small amber "New admin" / "You are now
    the admin" Crown badge whenever the most recent message is an
    admin_promoted system message. Variant differs for the promoted user vs
    other members. data-testid="badge-new-admin-<convId>" for tests.
  - Imported Crown from lucide-react.
- artifacts/teaboy-os/src/locales/en.json + ar.json
  - Added bilingual strings under chat.actions:
    youAreAdminTitle, youAreAdminDescription, newAdminBadge, newAdminLabel.

Implementation notes:
- lastMessage.meta is already returned by GET /conversations and includes the
  promoted user (id, displayNameAr/En, username), so no API changes were needed.
- Regenerated api-client-react via `pnpm --filter @workspace/api-spec run
  codegen` (pre-existing stale codegen was failing typecheck unrelated to this
  task, now clean for chat.tsx).
- Did not add automated tests; the existing follow-up task "Make sure the
  leave-and-handoff flow stays working with automated tests" already covers it.

Replit-Task-Id: 71495f44-8c6c-4f7f-8f48-6400630d1203
2026-04-21 11:53:59 +00:00
Riyadh 18c0518d3b Task #48: Let admins page through more than the last 100 opens
Added offset/limit pagination to the two admin app-opens drill-in
endpoints so admins can investigate spikes that span more than the
default 100 most-recent opens.

Backend (artifacts/api-server/src/routes/stats.ts):
- New parsePaging() helper validates `limit` (1..200, default 100) and
  `offset` (>=0, default 0); invalid values return 400.
- Both `/stats/admin/app-opens/by-app/:appId` and
  `/stats/admin/app-opens/by-user/:userId` accept the new params, apply
  `.limit(limit).offset(offset)`, and return `limit`, `offset`, and a
  `nextOffset` (number | null) computed from `totalCount`.
- Added a stable secondary sort (`id desc`) so paged results don't
  shuffle when timestamps tie.

Spec & client (lib/api-spec/openapi.yaml + regenerated clients):
- Added `limit`/`offset` query params and `limit`/`offset`/`nextOffset`
  response fields to AdminAppOpensByApp/AdminAppOpensByUser.
- Re-ran `pnpm --filter @workspace/api-spec run codegen`.

Frontend (artifacts/teaboy-os/src/pages/admin.tsx + locales):
- AppOpensDrillIn / UserOpensDrillIn now accumulate extra pages in
  local state and expose a "Load more" button via a shared
  LoadMoreSection footer that also shows "Showing X of Y".
- Extra pages are fetched via the generated `getAdminAppOpensByApp` /
  `getAdminAppOpensByUser` functions; accumulated state resets when
  the appId/userId or stats query params change.
- Added en/ar translations for `loadMore`, `loadMoreError`, `shownOf`.

Tests:
- New artifacts/api-server/tests/admin-app-opens-pagination.test.mjs
  covers happy-path paging for both endpoints, the default page size,
  and 400 responses for invalid limit/offset.
- All 10 api-server tests pass; full workspace typecheck passes.
2026-04-21 11:48:40 +00:00
riyadhafraa 216ff65e04 Task #48: Let admins page through more than the last 100 opens
Added offset/limit pagination to the two admin app-opens drill-in
endpoints so admins can investigate spikes that span more than the
default 100 most-recent opens.

Backend (artifacts/api-server/src/routes/stats.ts):
- New parsePaging() helper validates `limit` (1..200, default 100) and
  `offset` (>=0, default 0); invalid values return 400.
- Both `/stats/admin/app-opens/by-app/:appId` and
  `/stats/admin/app-opens/by-user/:userId` accept the new params, apply
  `.limit(limit).offset(offset)`, and return `limit`, `offset`, and a
  `nextOffset` (number | null) computed from `totalCount`.
- Added a stable secondary sort (`id desc`) so paged results don't
  shuffle when timestamps tie.

Spec & client (lib/api-spec/openapi.yaml + regenerated clients):
- Added `limit`/`offset` query params and `limit`/`offset`/`nextOffset`
  response fields to AdminAppOpensByApp/AdminAppOpensByUser.
- Re-ran `pnpm --filter @workspace/api-spec run codegen`.

Frontend (artifacts/teaboy-os/src/pages/admin.tsx + locales):
- AppOpensDrillIn / UserOpensDrillIn now accumulate extra pages in
  local state and expose a "Load more" button via a shared
  LoadMoreSection footer that also shows "Showing X of Y".
- Extra pages are fetched via the generated `getAdminAppOpensByApp` /
  `getAdminAppOpensByUser` functions; accumulated state resets when
  the appId/userId or stats query params change.
- Added en/ar translations for `loadMore`, `loadMoreError`, `shownOf`.

Tests:
- New artifacts/api-server/tests/admin-app-opens-pagination.test.mjs
  covers happy-path paging for both endpoints, the default page size,
  and 400 responses for invalid limit/offset.
- All 10 api-server tests pass; full workspace typecheck passes.

Replit-Task-Id: b6382efe-765f-4689-8c93-196fee253f63
2026-04-21 11:48:40 +00:00