Remove the 'X' close button from the clock widget and refactor its props, simplifying the component by removing unused `onHide` and `hideLabel` functionalities.
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.
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).
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).
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).
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).
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.
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
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.
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.
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.
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.
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.
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.
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.
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
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.
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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.
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
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)
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
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.
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
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.
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
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.
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
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.
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
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.
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