1b23d3a8e38562dc5f270999cf9a46d11e9d3c51
2231 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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.
|
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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) |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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.
|
||
|
|
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
|
||
|
|
fbfe2c7ef5 | Git commit prior to merge | ||
|
|
1055866105 | Git commit prior to merge | ||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
2fcaa27fbd |
Make admin recent-opens drill-in rows clickable
Task: #47 — Let admins jump from a recent open straight to that user. Changes (artifacts/teaboy-os/src/pages/admin.tsx): - AppOpensDrillIn: each user row in the per-app recent-opens popup is now a focusable button. Clicking jumps to that user's row in the Users list (closes the popup, then calls the existing handleSelectUserFromDashboard via a new onJumpUser prop), reusing the highlight behavior that already powers the leaderboard. - UserOpensDrillIn: each app row in the per-user recent-opens popup is now a focusable button. Clicking opens that app's edit modal (closes the popup, then calls the existing handleSelectAppFromDashboard via a new onJumpApp prop), mirroring the leaderboard click behavior. - Added type="button", hover/focus styles, and aria-labels reusing the existing admin.dashboard.viewUserDetails / admin.dashboard.viewAppDetails translation keys (already localized for both en and ar). The footer "Edit app" / "View user" buttons remain unchanged and keep working alongside the per-row clicks. No new translation keys, schema changes, or API changes were needed — AppOpenByAppEntry already exposes userId and AppOpenByUserEntry already exposes appId. Verification: tsc --noEmit on teaboy-os shows no new errors from these edits (only the same pre-existing errors that were present before this task). E2E was not run because the change is a thin wiring of existing handlers and admin login requires custom session setup beyond this task's scope. |
||
|
|
ce0819cfaa |
Make admin recent-opens drill-in rows clickable
Task: #47 — Let admins jump from a recent open straight to that user. Changes (artifacts/teaboy-os/src/pages/admin.tsx): - AppOpensDrillIn: each user row in the per-app recent-opens popup is now a focusable button. Clicking jumps to that user's row in the Users list (closes the popup, then calls the existing handleSelectUserFromDashboard via a new onJumpUser prop), reusing the highlight behavior that already powers the leaderboard. - UserOpensDrillIn: each app row in the per-user recent-opens popup is now a focusable button. Clicking opens that app's edit modal (closes the popup, then calls the existing handleSelectAppFromDashboard via a new onJumpApp prop), mirroring the leaderboard click behavior. - Added type="button", hover/focus styles, and aria-labels reusing the existing admin.dashboard.viewUserDetails / admin.dashboard.viewAppDetails translation keys (already localized for both en and ar). The footer "Edit app" / "View user" buttons remain unchanged and keep working alongside the per-row clicks. No new translation keys, schema changes, or API changes were needed — AppOpenByAppEntry already exposes userId and AppOpenByUserEntry already exposes appId. Verification: tsc --noEmit on teaboy-os shows no new errors from these edits (only the same pre-existing errors that were present before this task). E2E was not run because the change is a thin wiring of existing handlers and admin login requires custom session setup beyond this task's scope. Replit-Task-Id: 5db0964e-95fa-4199-9337-631cd1a790ec |
||
|
|
39f99a80a7 |
Let leaving admins choose who takes over the group (task #46)
Original ask: when the only admin leaves a group, let them pick a
specific successor instead of always auto-promoting the longest-tenured
member. Keep "auto" available, with bilingual labels.
Changes:
- lib/api-spec/openapi.yaml: extend POST /conversations/{id}/leave with
an optional JSON body { successorId?: number | null }.
- Regenerated lib/api-client-react and lib/api-zod via api-spec codegen.
- artifacts/api-server/src/routes/conversations.ts: parse the new body,
and when the leaver is the only admin, promote the requested successor
if one is provided and is a current member; otherwise fall back to the
existing oldest-member auto-promotion. Returns 400 if successorId is
not a remaining member.
- artifacts/teaboy-os/src/pages/chat.tsx:
- Track a successorChoice state ("auto" | userId).
- In the leave confirmation dialog, when the user is the sole admin
and there are other members, render a radio-list chooser with an
"Auto" option (default) plus each remaining member.
- Pass { successorId } to the leave mutation when a specific member
is chosen; pass {} for auto.
- Reset choice on success.
- artifacts/teaboy-os/src/locales/{en,ar}.json: added successorTitle,
successorHelp, successorAuto strings.
Verification:
- pnpm -w run typecheck passes for libs and all artifacts.
- Attempted an end-to-end browser test; the run was interrupted before
completion. Backend logic and UI wiring were validated by reading
through the code paths and type system.
|
||
|
|
0b7593bbc2 |
Let leaving admins choose who takes over the group (task #46)
Original ask: when the only admin leaves a group, let them pick a
specific successor instead of always auto-promoting the longest-tenured
member. Keep "auto" available, with bilingual labels.
Changes:
- lib/api-spec/openapi.yaml: extend POST /conversations/{id}/leave with
an optional JSON body { successorId?: number | null }.
- Regenerated lib/api-client-react and lib/api-zod via api-spec codegen.
- artifacts/api-server/src/routes/conversations.ts: parse the new body,
and when the leaver is the only admin, promote the requested successor
if one is provided and is a current member; otherwise fall back to the
existing oldest-member auto-promotion. Returns 400 if successorId is
not a remaining member.
- artifacts/teaboy-os/src/pages/chat.tsx:
- Track a successorChoice state ("auto" | userId).
- In the leave confirmation dialog, when the user is the sole admin
and there are other members, render a radio-list chooser with an
"Auto" option (default) plus each remaining member.
- Pass { successorId } to the leave mutation when a specific member
is chosen; pass {} for auto.
- Reset choice on success.
- artifacts/teaboy-os/src/locales/{en,ar}.json: added successorTitle,
successorHelp, successorAuto strings.
Verification:
- pnpm -w run typecheck passes for libs and all artifacts.
- Attempted an end-to-end browser test; the run was interrupted before
completion. Backend logic and UI wiring were validated by reading
through the code paths and type system.
Replit-Task-Id: c9065bc1-ab4e-4a3b-a865-81754f5c2e5a
|
||
|
|
5a0a573609 |
Add notes functionality to the application with CRUD operations and labeling
Integrates a new Notes feature, including backend API routes for notes and labels, database schema updates, frontend UI components for creating, viewing, editing, and deleting notes, and internationalization support for notes in both English and Arabic. |
||
|
|
a864b84567 |
Add notes functionality to the application with CRUD operations and labeling
Integrates a new Notes feature, including backend API routes for notes and labels, database schema updates, frontend UI components for creating, viewing, editing, and deleting notes, and internationalization support for notes in both English and Arabic. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 4ab7f101-06a9-4dd7-94c8-617f1751327c Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fYGOSe0 Replit-Helium-Checkpoint-Created: true |
||
|
|
75147c8f24 |
Task #45: Tapping a chat notification opens the matching conversation
- notifications.tsx: When a notification is clicked, if it has relatedType === "conversation" and a relatedId, navigate to /chat?c=<conversationId> in addition to marking it read. Other notification types continue to behave as before (just mark read). - chat.tsx: Read the optional `c` query parameter via wouter's useSearch hook. When present and valid, set it as the selectedConvId so the conversation opens automatically, then replace the URL back to /chat so refreshing or navigating away doesn't keep re-opening it. No API or schema changes required; notification records already carry relatedType/relatedId for chat notifications. |
||
|
|
8729cae01a |
Task #45: Tapping a chat notification opens the matching conversation
- notifications.tsx: When a notification is clicked, if it has relatedType === "conversation" and a relatedId, navigate to /chat?c=<conversationId> in addition to marking it read. Other notification types continue to behave as before (just mark read). - chat.tsx: Read the optional `c` query parameter via wouter's useSearch hook. When present and valid, set it as the selectedConvId so the conversation opens automatically, then replace the URL back to /chat so refreshing or navigating away doesn't keep re-opening it. No API or schema changes required; notification records already carry relatedType/relatedId for chat notifications. Replit-Task-Id: 2fda1864-99d6-4f07-a3d9-aa3021d5493f |
||
|
|
999d1a3e50 |
Push notification_created socket event for instant bell badge updates
Original task: Make the bell badge update instantly when a chat
notification arrives.
Changes:
- artifacts/api-server/src/routes/conversations.ts: After
createMessageNotifications inserts the rows, emit a
`notification_created` event to each recipient's `user:{id}` room
(carrying type/relatedId/relatedType for future routing).
- artifacts/teaboy-os/src/hooks/use-notifications-socket.ts: New hook
that opens a global socket connection (when the user is logged in)
and invalidates getListNotificationsQueryKey() and
getGetHomeStatsQueryKey() whenever `notification_created` fires.
- artifacts/teaboy-os/src/App.tsx: Mounts the hook via a small
NotificationsSocketBridge component inside AuthProvider so the
listener is active on every page (home, notifications, services,
etc.), not just /chat.
Notes:
- Socket join already happens server-side on connect
(`socket.join('user:{userId}')`), so no API server topology change
was needed.
- Pre-existing TS errors in api-server and teaboy-os are unrelated to
this change.
|
||
|
|
1411486602 |
Push notification_created socket event for instant bell badge updates
Original task: Make the bell badge update instantly when a chat
notification arrives.
Changes:
- artifacts/api-server/src/routes/conversations.ts: After
createMessageNotifications inserts the rows, emit a
`notification_created` event to each recipient's `user:{id}` room
(carrying type/relatedId/relatedType for future routing).
- artifacts/teaboy-os/src/hooks/use-notifications-socket.ts: New hook
that opens a global socket connection (when the user is logged in)
and invalidates getListNotificationsQueryKey() and
getGetHomeStatsQueryKey() whenever `notification_created` fires.
- artifacts/teaboy-os/src/App.tsx: Mounts the hook via a small
NotificationsSocketBridge component inside AuthProvider so the
listener is active on every page (home, notifications, services,
etc.), not just /chat.
Notes:
- Socket join already happens server-side on connect
(`socket.join('user:{userId}')`), so no API server topology change
was needed.
- Pre-existing TS errors in api-server and teaboy-os are unrelated to
this change.
Replit-Task-Id: ff3973f9-1c9d-4283-8091-bfeb3eb0d6a4
|
||
|
|
6316893c0e |
Task #43: Stop counting system messages as unread
Problem: Group rename, member-add, and member-remove events insert rows into the messages table with the actor as senderId, which made them increment the unread badge for everyone else just like a normal chat message. That created notification noise for routine admin tweaks. Fix: In `artifacts/api-server/src/routes/conversations.ts`, the `buildConversationDetails` unread count query now restricts to `messages.kind = 'user'`, so only real user chat messages contribute to the unread count returned for the conversation list / bell badge. Notes / non-changes: - The conversation-list "last message" preview is intentionally left unfiltered, so the most recent activity (including system messages) still surfaces in the list — matches the task's "Done looks like". - Push-style chat notifications (`createMessageNotifications`) are only invoked from the user send-message route, never from `insertAndEmitSystemMessage`, so no additional guard is required for system events today. - Frontend chat.tsx already renders system messages distinctly and shows them in the list preview; no UI change needed. |
||
|
|
ce1fb84365 |
Task #43: Stop counting system messages as unread
Problem: Group rename, member-add, and member-remove events insert rows into the messages table with the actor as senderId, which made them increment the unread badge for everyone else just like a normal chat message. That created notification noise for routine admin tweaks. Fix: In `artifacts/api-server/src/routes/conversations.ts`, the `buildConversationDetails` unread count query now restricts to `messages.kind = 'user'`, so only real user chat messages contribute to the unread count returned for the conversation list / bell badge. Notes / non-changes: - The conversation-list "last message" preview is intentionally left unfiltered, so the most recent activity (including system messages) still surfaces in the list — matches the task's "Done looks like". - Push-style chat notifications (`createMessageNotifications`) are only invoked from the user send-message route, never from `insertAndEmitSystemMessage`, so no additional guard is required for system events today. - Frontend chat.tsx already renders system messages distinctly and shows them in the list preview; no UI change needed. Replit-Task-Id: 3734d73b-2df7-4f32-9655-6c7a9e23536c |
||
|
|
e37f926581 |
Task #42: Tell members when someone leaves a group on their own
Original task: Post a bilingual system message into the group chat when a member leaves via POST /api/conversations/:id/leave, with real-time delivery via Socket.IO and rendering in the chat thread. Findings: - The leave handler in artifacts/api-server/src/routes/conversations.ts already inserts a `member_left` system message (and `admin_promoted` when a successor is auto-promoted) and emits via Socket.IO using insertAndEmitSystemMessage. Direct conversations and last-leaver cleanup paths are correctly skipped. - artifacts/teaboy-os/src/pages/chat.tsx renderSystemMessage already handles `member_left`, `admin_promoted`, and `adminPromotedAfterLeave`. - Bilingual locale keys exist in src/locales/en.json and ar.json under chat.system.memberLeft / adminPromoted / adminPromotedAfterLeave. Change made: - Extended the MessageWithSender.kind enum in lib/api-spec/openapi.yaml to include `member_left` and `admin_promoted` (previously only user/group_renamed/members_added/member_removed were declared). - Re-ran `pnpm --filter @workspace/api-spec run codegen` to regenerate api-zod and api-client-react schemas so the spec, runtime validation, and client types stay in sync. - Verified `pnpm -w run typecheck` passes across all packages. No behavioural code changes were necessary; the feature was already implemented end-to-end and only the API spec enum needed to catch up. |
||
|
|
62b79c7e7d |
Task #42: Tell members when someone leaves a group on their own
Original task: Post a bilingual system message into the group chat when a member leaves via POST /api/conversations/:id/leave, with real-time delivery via Socket.IO and rendering in the chat thread. Findings: - The leave handler in artifacts/api-server/src/routes/conversations.ts already inserts a `member_left` system message (and `admin_promoted` when a successor is auto-promoted) and emits via Socket.IO using insertAndEmitSystemMessage. Direct conversations and last-leaver cleanup paths are correctly skipped. - artifacts/teaboy-os/src/pages/chat.tsx renderSystemMessage already handles `member_left`, `admin_promoted`, and `adminPromotedAfterLeave`. - Bilingual locale keys exist in src/locales/en.json and ar.json under chat.system.memberLeft / adminPromoted / adminPromotedAfterLeave. Change made: - Extended the MessageWithSender.kind enum in lib/api-spec/openapi.yaml to include `member_left` and `admin_promoted` (previously only user/group_renamed/members_added/member_removed were declared). - Re-ran `pnpm --filter @workspace/api-spec run codegen` to regenerate api-zod and api-client-react schemas so the spec, runtime validation, and client types stay in sync. - Verified `pnpm -w run typecheck` passes across all packages. No behavioural code changes were necessary; the feature was already implemented end-to-end and only the API spec enum needed to catch up. Replit-Task-Id: 40fccb3a-4fc0-4ad7-a6e9-d1ac67ef7769 |
||
|
|
1457e99a49 |
Add admin recent-opens drill-in popup for top apps and users
Task #41: Show a usage history popup when admins click a top apps row or a most-active users row on the admin dashboard. Changes: - API: extracted the existing range/from/to parser in artifacts/api-server/src/routes/stats.ts into a shared parseRangeWindow() helper so the same window logic powers the existing /stats/admin endpoint and two new ones. - API: added GET /stats/admin/app-opens/by-app/:appId returning the last 100 opens for an app inside the selected window, including the user (id, username, displayName, avatarUrl) and a totalCount. requireAuth + requireAdmin guarded. - API: added GET /stats/admin/app-opens/by-user/:userId with the same shape but per-app metadata for each open. - Spec: added matching paths and AdminAppOpensByApp / AdminAppOpensByUser schemas in lib/api-spec/openapi.yaml, reran orval codegen for api-client-react and api-zod. - UI: in artifacts/teaboy-os/src/pages/admin.tsx the Top apps and Most active users leaderboard rows now open a drill-in modal instead of immediately navigating away. The modal shows a scrollable timeline of recent opens (timestamp + user/app), respects the dashboard's current range selector (7d/30d/90d/custom), and exposes an "Edit app" / "View user" footer button that preserves the previous jump-to behaviour. Loading, error, and empty states are handled. - i18n: added admin.dashboard.drillIn keys in en.json and ar.json (titles, subtitle, empty/error, footer buttons). Verification: pnpm run typecheck passes. Follow-ups proposed: #47 (clickable rows inside the popup), #48 (paginate beyond 100 opens). |
||
|
|
b6a0dc6ab9 |
Add admin recent-opens drill-in popup for top apps and users
Task #41: Show a usage history popup when admins click a top apps row or a most-active users row on the admin dashboard. Changes: - API: extracted the existing range/from/to parser in artifacts/api-server/src/routes/stats.ts into a shared parseRangeWindow() helper so the same window logic powers the existing /stats/admin endpoint and two new ones. - API: added GET /stats/admin/app-opens/by-app/:appId returning the last 100 opens for an app inside the selected window, including the user (id, username, displayName, avatarUrl) and a totalCount. requireAuth + requireAdmin guarded. - API: added GET /stats/admin/app-opens/by-user/:userId with the same shape but per-app metadata for each open. - Spec: added matching paths and AdminAppOpensByApp / AdminAppOpensByUser schemas in lib/api-spec/openapi.yaml, reran orval codegen for api-client-react and api-zod. - UI: in artifacts/teaboy-os/src/pages/admin.tsx the Top apps and Most active users leaderboard rows now open a drill-in modal instead of immediately navigating away. The modal shows a scrollable timeline of recent opens (timestamp + user/app), respects the dashboard's current range selector (7d/30d/90d/custom), and exposes an "Edit app" / "View user" footer button that preserves the previous jump-to behaviour. Loading, error, and empty states are handled. - i18n: added admin.dashboard.drillIn keys in en.json and ar.json (titles, subtitle, empty/error, footer buttons). Verification: pnpm run typecheck passes. Follow-ups proposed: #47 (clickable rows inside the popup), Replit-Task-Id: f2fbe652-a788-4f25-9fb2-9ef2c535c8da #48 (paginate beyond 100 opens). |