Add a per-user 12/24-hour clock preference that complements the existing
clock-style preference and is honored by every clock variant on the home
screen.
Schema & API
- Added `clockHour12 boolean` (nullable) column to `users` table
(lib/db/src/schema/users.ts) and synced via direct `ALTER TABLE`
because `drizzle-kit push` was blocked by an unrelated interactive
rename prompt for the existing `app_opens` table.
- Extended OpenAPI `AuthUser` and `UserProfile` with `clockHour12`,
added `UpdateClockHour12Body` schema, and a new
`PATCH /auth/me/clock-hour12` endpoint. Regenerated zod + react-query
clients via `pnpm --filter @workspace/api-spec run codegen`.
- Implemented the new route handler in
`artifacts/api-server/src/routes/auth.ts`; `buildAuthUser` now
surfaces `clockHour12`.
Frontend
- `lib/i18n-format.ts` no longer hard-codes `hour12: false`; callers
may pass `hour12` in options. Default remains 24-hour to keep all
other timestamps unchanged (chat etc. left as-is — see follow-up).
- `components/clock.tsx` exports `resolveClockHour12` and threads a new
`hour12` prop through `Clock` and `AnalogClockWidget`. All five
variants (full/digital/digital-no-seconds/analog/minimal) plus the
large analog widget now honor the choice.
- `components/clock-style-picker.tsx` gained a 12-hour / 24-hour
segmented toggle that calls the new endpoint with optimistic cache
updates. The variant previews also reflect the active hour format.
- `pages/home.tsx` passes `user.clockHour12` to the header clock,
widget, and picker.
- Added `home.clockStyle.hourFormat.{label,h12,h24}` strings in EN and
AR. Arabic uses Latin digits and "ص/م" via Intl's localized
dayPeriod.
Verification
- `pnpm -w run typecheck` passes.
- E2E test: logged in, switched to 12-hour, verified AM/PM in header
and previews, reloaded to confirm persistence, switched back to
24-hour, reloaded again — all green.
Add a per-user 12/24-hour clock preference that complements the existing
clock-style preference and is honored by every clock variant on the home
screen.
Schema & API
- Added `clockHour12 boolean` (nullable) column to `users` table
(lib/db/src/schema/users.ts) and synced via direct `ALTER TABLE`
because `drizzle-kit push` was blocked by an unrelated interactive
rename prompt for the existing `app_opens` table.
- Extended OpenAPI `AuthUser` and `UserProfile` with `clockHour12`,
added `UpdateClockHour12Body` schema, and a new
`PATCH /auth/me/clock-hour12` endpoint. Regenerated zod + react-query
clients via `pnpm --filter @workspace/api-spec run codegen`.
- Implemented the new route handler in
`artifacts/api-server/src/routes/auth.ts`; `buildAuthUser` now
surfaces `clockHour12`.
Frontend
- `lib/i18n-format.ts` no longer hard-codes `hour12: false`; callers
may pass `hour12` in options. Default remains 24-hour to keep all
other timestamps unchanged (chat etc. left as-is — see follow-up).
- `components/clock.tsx` exports `resolveClockHour12` and threads a new
`hour12` prop through `Clock` and `AnalogClockWidget`. All five
variants (full/digital/digital-no-seconds/analog/minimal) plus the
large analog widget now honor the choice.
- `components/clock-style-picker.tsx` gained a 12-hour / 24-hour
segmented toggle that calls the new endpoint with optimistic cache
updates. The variant previews also reflect the active hour format.
- `pages/home.tsx` passes `user.clockHour12` to the header clock,
widget, and picker.
- Added `home.clockStyle.hourFormat.{label,h12,h24}` strings in EN and
AR. Arabic uses Latin digits and "ص/م" via Intl's localized
dayPeriod.
Verification
- `pnpm -w run typecheck` passes.
- E2E test: logged in, switched to 12-hour, verified AM/PM in header
and previews, reloaded to confirm persistence, switched back to
24-hour, reloaded again — all green.
Replit-Task-Id: 03ea8cbe-ace7-4d36-afc5-49ebc9706c67
Task #24: Remember the admin's last-used dashboard time range.
The admin dashboard's range selector (7d/30d/90d/custom) was resetting to
"Last 7 days" on every visit. Now the selected preset range is persisted in
localStorage under a per-user key (`admin.statsRange.<userId>`) so each admin
sees their last choice on return.
Implementation notes:
- Edited only artifacts/teaboy-os/src/pages/admin.tsx.
- Added two effects: one hydrates the stored value once `user.id` is
available (auth loads asynchronously), the other writes to localStorage
whenever the range changes after hydration.
- Only the preset values (7d/30d/90d) are persisted; "custom" is intentionally
not stored since the custom dates are session-scoped and would feel stale on
return. After hydration, switching to "custom" leaves the previously stored
preset untouched so the next visit still restores the last preset.
- Used a hydration guard to avoid clobbering stored values with the default
"7d" before the load effect runs.
Task #24: Remember the admin's last-used dashboard time range.
The admin dashboard's range selector (7d/30d/90d/custom) was resetting to
"Last 7 days" on every visit. Now the selected preset range is persisted in
localStorage under a per-user key (`admin.statsRange.<userId>`) so each admin
sees their last choice on return.
Implementation notes:
- Edited only artifacts/teaboy-os/src/pages/admin.tsx.
- Added two effects: one hydrates the stored value once `user.id` is
available (auth loads asynchronously), the other writes to localStorage
whenever the range changes after hydration.
- Only the preset values (7d/30d/90d) are persisted; "custom" is intentionally
not stored since the custom dates are session-scoped and would feel stale on
return. After hydration, switching to "custom" leaves the previously stored
preset untouched so the next visit still restores the last preset.
- Used a hydration guard to avoid clobbering stored values with the default
"7d" before the load effect runs.
Replit-Task-Id: a29d7e30-8111-4edd-9307-3262c4dbc236
- OpenAPI: added `custom` to range enum, `from`/`to` query params, and `rangeFrom`/`rangeTo` on AdminStats; ran codegen.
- API (`artifacts/api-server/src/routes/stats.ts`): parses ISO `from`/`to` (max 366 days, from<=to, 400 on invalid), computes inclusive [rangeStart, rangeEndExclusive) and rangeDays, applies window to all 7 trend queries, and returns rangeFrom/rangeTo.
- Frontend (`artifacts/teaboy-os/src/pages/admin.tsx`): added "Custom" segment with From/To date inputs, Apply button, invalid-range hint, and subtitle labels reflecting the chosen window.
- i18n: added range.custom, range.customLabel, prevRange.custom, customRange.{from,to,apply,invalid} for en + ar.
- Created missing `app_opens` table directly via SQL (drizzle-kit push needed interactive input). Reset admin password hash so seed account could log in.
- Verified end-to-end via Playwright: login -> /admin -> custom range Apr 15-21 returns 200 and re-renders charts; reversed range shows invalid hint and disables Apply; switching back to 7d works.
Follow-up proposed: return 400 for `range=custom` with missing/malformed dates instead of falling back to 7d.
- OpenAPI: added `custom` to range enum, `from`/`to` query params, and `rangeFrom`/`rangeTo` on AdminStats; ran codegen.
- API (`artifacts/api-server/src/routes/stats.ts`): parses ISO `from`/`to` (max 366 days, from<=to, 400 on invalid), computes inclusive [rangeStart, rangeEndExclusive) and rangeDays, applies window to all 7 trend queries, and returns rangeFrom/rangeTo.
- Frontend (`artifacts/teaboy-os/src/pages/admin.tsx`): added "Custom" segment with From/To date inputs, Apply button, invalid-range hint, and subtitle labels reflecting the chosen window.
- i18n: added range.custom, range.customLabel, prevRange.custom, customRange.{from,to,apply,invalid} for en + ar.
- Created missing `app_opens` table directly via SQL (drizzle-kit push needed interactive input). Reset admin password hash so seed account could log in.
- Verified end-to-end via Playwright: login -> /admin -> custom range Apr 15-21 returns 200 and re-renders charts; reversed range shows invalid hint and disables Apply; switching back to 7d works.
Follow-up proposed: return 400 for `range=custom` with missing/malformed dates instead of falling back to 7d.
Replit-Task-Id: a50d8a1e-60ad-43b2-b8ea-4eeae6ef5dd0
Adds a new `AnalogClockWidget` component with Roman numerals and a sweeping second hand, integrated into the home page. The widget's visibility can now be toggled via the `ClockStylePicker` and is persisted in `localStorage`.
Adds a new `AnalogClockWidget` component with Roman numerals and a sweeping second hand, integrated into the home page. The widget's visibility can now be toggled via the `ClockStylePicker` and is persisted in `localStorage`.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0a1df300-b156-461b-90d5-e9874f25113f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/YPEna1J
Replit-Helium-Checkpoint-Created: true
Original task: verify POST /api/apps/:id/open behaves correctly under slow
networks (the keepalive POST must survive the user navigating away),
unauthenticated callers (401 with no row inserted), and unknown app ids
(404 with no row inserted).
Changes
- New committed test file artifacts/api-server/tests/apps-open.test.mjs
using Node's built-in node:test runner (no new test framework added):
* happy path: authenticated POST returns 204 and inserts an app_opens row
* unauthenticated POST returns 401 and inserts no row
* authenticated POST to a non-existent app id (max(id)+100000) returns 404
and inserts no row
* slow-network simulation: opens a raw http.request to /api/apps/:id/open,
aborts the socket ~50 ms after sending so the client never reads the
response (mimicking a navigation-aborted keepalive POST), then asserts
the server still inserted the row. This proves the route's
`await db.insert(...)` runs to completion independently of whether the
client is still around to read the 204.
- The tests create a dedicated test user with a precomputed bcrypt hash for
"TestPass123!", assign the standard "user" role, log in via
POST /api/auth/login to obtain a connect.sid cookie, run the four cases,
and clean up (app_opens / user_roles / users) in an `after` hook.
- Added `pg` as a devDependency on @workspace/api-server (used by the
tests for direct DB assertions) and a `test` script:
`node --test 'tests/**/*.test.mjs'`.
- Also ran in-browser end-to-end coverage via the testing skill that
exercised the keepalive + wouter navigation flow against a live home
page with a 3 s route delay; that run also passed.
Schema drift fixed during the run
- The dev DB was missing the `app_opens` table and the `users.clock_style`
column referenced by the running schema. `pnpm --filter @workspace/db
push` blocked on an interactive rename/create prompt that could not be
answered non-interactively, so I brought the dev DB in line with the
Drizzle schema using idempotent SQL (CREATE TABLE IF NOT EXISTS for
app_opens with its two indexes; ALTER TABLE users ADD COLUMN IF NOT
EXISTS clock_style varchar(30)). No schema files were modified.
No production code changes were required — the existing route already
returns 401/404/204 correctly and the tests now lock that behavior in.
Original task: verify POST /api/apps/:id/open behaves correctly under slow
networks (the keepalive POST must survive the user navigating away),
unauthenticated callers (401 with no row inserted), and unknown app ids
(404 with no row inserted).
Changes
- New committed test file artifacts/api-server/tests/apps-open.test.mjs
using Node's built-in node:test runner (no new test framework added):
* happy path: authenticated POST returns 204 and inserts an app_opens row
* unauthenticated POST returns 401 and inserts no row
* authenticated POST to a non-existent app id (max(id)+100000) returns 404
and inserts no row
* slow-network simulation: opens a raw http.request to /api/apps/:id/open,
aborts the socket ~50 ms after sending so the client never reads the
response (mimicking a navigation-aborted keepalive POST), then asserts
the server still inserted the row. This proves the route's
`await db.insert(...)` runs to completion independently of whether the
client is still around to read the 204.
- The tests create a dedicated test user with a precomputed bcrypt hash for
"TestPass123!", assign the standard "user" role, log in via
POST /api/auth/login to obtain a connect.sid cookie, run the four cases,
and clean up (app_opens / user_roles / users) in an `after` hook.
- Added `pg` as a devDependency on @workspace/api-server (used by the
tests for direct DB assertions) and a `test` script:
`node --test 'tests/**/*.test.mjs'`.
- Also ran in-browser end-to-end coverage via the testing skill that
exercised the keepalive + wouter navigation flow against a live home
page with a 3 s route delay; that run also passed.
Schema drift fixed during the run
- The dev DB was missing the `app_opens` table and the `users.clock_style`
column referenced by the running schema. `pnpm --filter @workspace/db
push` blocked on an interactive rename/create prompt that could not be
answered non-interactively, so I brought the dev DB in line with the
Drizzle schema using idempotent SQL (CREATE TABLE IF NOT EXISTS for
app_opens with its two indexes; ALTER TABLE users ADD COLUMN IF NOT
EXISTS clock_style varchar(30)). No schema files were modified.
No production code changes were required — the existing route already
returns 401/404/204 correctly and the tests now lock that behavior in.
Replit-Task-Id: b7422abb-cc1b-4727-b70b-cde090f1a748
Adds three new tables (notes, note_items, note_shares) for storing note data, checklist items, and sharing permissions. Includes API endpoints and UI considerations for creating, editing, viewing, and managing notes, with features like pinning, color-coding, archiving, and user sharing.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: f9708c33-0313-4d52-9aaf-39f564d4af34
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/pCDTgLS
Replit-Helium-Checkpoint-Created: true
Problem
- Dev environment login returned 500: `column "clock_style" does not
exist`. Task #20's schema change for the per-user clock style was
never applied to the dev DB because `scripts/post-merge.sh` runs
`drizzle-kit push` interactively, and drizzle-kit got stuck on a
prompt asking whether `app_opens` was a rename of `user_sessions`.
When the prompt couldn't be answered, the whole sync exited without
applying any pending changes — including the new column.
Changes
- Applied the missing column directly:
`ALTER TABLE users ADD COLUMN IF NOT EXISTS clock_style varchar(30);`
(nullable, no default — matches `lib/db/src/schema/users.ts`).
Verified column now present.
- scripts/post-merge.sh: switched
`pnpm --filter db push` → `pnpm --filter db run push-force`.
The `push-force` script (already defined in `lib/db/package.json`)
passes `--force` to drizzle-kit, which auto-accepts safe operations
and treats ambiguous renames as creates — which is the correct
behavior for our case (we genuinely have new tables, not renames).
This prevents the same class of failure from recurring after future
merges.
Verification
- Restarted the API server.
- `POST /api/auth/login` with admin/admin → HTTP 200, returns AuthUser
with `clockStyle: null` (frontend already falls back gracefully, no
home.tsx change needed).
- No new 500s in the API logs.
Out of scope (as planned)
- Backfilling a default value for existing rows.
- Resolving the underlying drizzle-kit `app_opens` rename detection
— `--force` sidesteps it correctly.
Problem
- Dev environment login returned 500: `column "clock_style" does not
exist`. Task #20's schema change for the per-user clock style was
never applied to the dev DB because `scripts/post-merge.sh` runs
`drizzle-kit push` interactively, and drizzle-kit got stuck on a
prompt asking whether `app_opens` was a rename of `user_sessions`.
When the prompt couldn't be answered, the whole sync exited without
applying any pending changes — including the new column.
Changes
- Applied the missing column directly:
`ALTER TABLE users ADD COLUMN IF NOT EXISTS clock_style varchar(30);`
(nullable, no default — matches `lib/db/src/schema/users.ts`).
Verified column now present.
- scripts/post-merge.sh: switched
`pnpm --filter db push` → `pnpm --filter db run push-force`.
The `push-force` script (already defined in `lib/db/package.json`)
passes `--force` to drizzle-kit, which auto-accepts safe operations
and treats ambiguous renames as creates — which is the correct
behavior for our case (we genuinely have new tables, not renames).
This prevents the same class of failure from recurring after future
merges.
Verification
- Restarted the API server.
- `POST /api/auth/login` with admin/admin → HTTP 200, returns AuthUser
with `clockStyle: null` (frontend already falls back gracefully, no
home.tsx change needed).
- No new 500s in the API logs.
Out of scope (as planned)
- Backfilling a default value for existing rows.
- Resolving the underlying drizzle-kit `app_opens` rename detection
— `--force` sidesteps it correctly.
Added two leaderboard panels to the admin dashboard that surface which
apps are most popular and which users drive the most activity in the
selected time range.
Backend (artifacts/api-server/src/routes/stats.ts):
- Extended GET /api/stats/admin to also return:
- topApps: top 5 apps by app_opens count, with id, slug, names,
iconName, color, count
- mostActiveUsers: top 5 users by app_opens count, with id, username,
displayNames, avatarUrl, count
- Both lists honor the existing `range` query param (7d/30d/90d) so
they stay in sync with the trend charts. Task wording said "last 7
days" because 7d is the default; using the selected range is a small
intentional improvement that matches the rest of the dashboard.
API spec (lib/api-spec/openapi.yaml):
- Added TopAppItem and TopUserItem schemas.
- Added topApps and mostActiveUsers to AdminStats and made them
required. Regenerated api-client-react and api-zod via codegen.
Frontend (artifacts/teaboy-os/src/pages/admin.tsx):
- Added two new panels to DashboardSection rendered in a 2-column grid
between the trend charts and the recent activity card.
- Each row shows rank, color/initial, name (i18n), count, and a
proportional progress bar. Empty state when no activity yet.
i18n (artifacts/teaboy-os/src/locales/{ar,en}.json):
- Added admin.dashboard.topApps, mostActiveUsers, *Subtitle,
leaderboardEmpty, openCount keys.
Verified with end-to-end test: admin login, dashboard renders both
panels with seeded data, range switch updates subtitles to "Last 30
days".
Added two leaderboard panels to the admin dashboard that surface which
apps are most popular and which users drive the most activity in the
selected time range.
Backend (artifacts/api-server/src/routes/stats.ts):
- Extended GET /api/stats/admin to also return:
- topApps: top 5 apps by app_opens count, with id, slug, names,
iconName, color, count
- mostActiveUsers: top 5 users by app_opens count, with id, username,
displayNames, avatarUrl, count
- Both lists honor the existing `range` query param (7d/30d/90d) so
they stay in sync with the trend charts. Task wording said "last 7
days" because 7d is the default; using the selected range is a small
intentional improvement that matches the rest of the dashboard.
API spec (lib/api-spec/openapi.yaml):
- Added TopAppItem and TopUserItem schemas.
- Added topApps and mostActiveUsers to AdminStats and made them
required. Regenerated api-client-react and api-zod via codegen.
Frontend (artifacts/teaboy-os/src/pages/admin.tsx):
- Added two new panels to DashboardSection rendered in a 2-column grid
between the trend charts and the recent activity card.
- Each row shows rank, color/initial, name (i18n), count, and a
proportional progress bar. Empty state when no activity yet.
i18n (artifacts/teaboy-os/src/locales/{ar,en}.json):
- Added admin.dashboard.topApps, mostActiveUsers, *Subtitle,
leaderboardEmpty, openCount keys.
Verified with end-to-end test: admin login, dashboard renders both
panels with seeded data, range switch updates subtitles to "Last 30
days".
Replit-Task-Id: c7b6aa4b-9242-443b-9802-a39ab0bc9547
Original task #20: Let each user pick their own home-screen clock
style (analog/digital/minimal/etc.), persisted on the user record
and restored on login / other devices. Default = "full".
Changes:
- DB: added `clock_style varchar(30)` (nullable) to `users`
(lib/db/src/schema/users.ts) and applied via direct ALTER TABLE
(drizzle-kit push had unrelated interactive prompts about
app_opens / user_sessions which were not safe to answer).
- OpenAPI: added `ClockStyle` enum (full/digital/digital-no-seconds
/analog/minimal), `UpdateClockStyleBody`, exposed `clockStyle`
on AuthUser and UserProfile, and added PATCH /auth/me/clock-style.
Regenerated typed client and zod schemas.
- API: `buildAuthUser` and `buildUserProfile` include `clockStyle`;
new authenticated endpoint updates the current user's style and
returns the refreshed AuthUser.
- Frontend: new `Clock` component (artifacts/teaboy-os/src/
components/clock.tsx) with five variants sharing a single `useNow`
tick hook, plus an SVG analog clock; honors existing
Latin-digit/locale formatting helpers. New `ClockStylePicker`
(popover) shown in the status bar with a live preview for each
option and optimistic update through the AuthUser query cache.
- home.tsx replaces the hard-coded clock block with `<Clock>` driven
by `user.clockStyle`; trigger button placed next to the language
toggle.
- i18n: added `home.clockStyle.label` and per-style option labels
to en.json and ar.json.
Verified via e2e: register → default "full" rendered → switch to
analog → reload → analog persists → switch to minimal → time-only
renders. RTL layout + Latin digits both correct after language
toggle.
Original task #20: Let each user pick their own home-screen clock
style (analog/digital/minimal/etc.), persisted on the user record
and restored on login / other devices. Default = "full".
Changes:
- DB: added `clock_style varchar(30)` (nullable) to `users`
(lib/db/src/schema/users.ts) and applied via direct ALTER TABLE
(drizzle-kit push had unrelated interactive prompts about
app_opens / user_sessions which were not safe to answer).
- OpenAPI: added `ClockStyle` enum (full/digital/digital-no-seconds
/analog/minimal), `UpdateClockStyleBody`, exposed `clockStyle`
on AuthUser and UserProfile, and added PATCH /auth/me/clock-style.
Regenerated typed client and zod schemas.
- API: `buildAuthUser` and `buildUserProfile` include `clockStyle`;
new authenticated endpoint updates the current user's style and
returns the refreshed AuthUser.
- Frontend: new `Clock` component (artifacts/teaboy-os/src/
components/clock.tsx) with five variants sharing a single `useNow`
tick hook, plus an SVG analog clock; honors existing
Latin-digit/locale formatting helpers. New `ClockStylePicker`
(popover) shown in the status bar with a live preview for each
option and optimistic update through the AuthUser query cache.
- home.tsx replaces the hard-coded clock block with `<Clock>` driven
by `user.clockStyle`; trigger button placed next to the language
toggle.
- i18n: added `home.clockStyle.label` and per-style option labels
to en.json and ar.json.
Verified via e2e: register → default "full" rendered → switch to
analog → reload → analog persists → switch to minimal → time-only
renders. RTL layout + Latin digits both correct after language
toggle.
Replit-Task-Id: 475a7439-b357-400d-805f-5f2fda20ed24
Adjust the AvatarFallback component in the chat page to correctly display initials for group conversations, using a Users icon for smaller groups and showing the first letter of the group name for larger ones.
Adjust the AvatarFallback component in the chat page to correctly display initials for group conversations, using a Users icon for smaller groups and showing the first letter of the group name for larger ones.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: e1f2ec36-f2c2-4be6-ac4f-bab54bcfb2f0
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/pCDTgLS
Replit-Helium-Checkpoint-Created: true
Original ask: in the chat page, give users a clear way to create a
group chat (the "+" button already opened a dialog, but it had no name
field, no Direct/Group distinction, and no way to tell groups apart in
the conversation list).
Backend, DB schema, and OpenAPI already supported groups
(isGroup/nameAr/nameEn/participantIds + isAdmin on creator), so this
task was UI-only.
Changes:
- artifacts/teaboy-os/src/pages/chat.tsx — rebuilt the "New
conversation" dialog with:
* Direct / Group segmented tabs (default Direct)
* Bilingual group name fields (AR + EN, at least one required)
* Search-filterable participant list (matches username,
displayNameAr, displayNameEn)
* Custom checkbox UI; direct mode is single-select (clicking
another user replaces selection), group mode multi-select
* Live participant counter and inline validation messages
(pickOnePerson / minTwoMembers / needGroupName)
* Submit button driven by validation; closes on backdrop click
and resets state on open/close
* Switching to Direct mode clears stale group-name input
- Conversation list rows and chat header now show a "Group" / "مجموعة"
badge for is_group conversations; group avatar uses a Users icon
instead of name initials. Direct chats unchanged.
- New convDisplayName helper: prefers active-language name, falls back
to other language, then participant names, then "Direct Message".
- artifacts/teaboy-os/src/locales/{ar,en}.json — added all new strings
under chat.* (modeDirect, modeGroup, groupNameAr/En, placeholders,
searchUsersPlaceholder, participantsCount, validation.*, create,
noUsersFound). Updated EN groupChat label to compact "Group".
No backend, schema, codegen, or OpenAPI changes. Regenerated api
client locally only because pre-existing pages had stale types from
prior merges.
Verification: e2e test passed — login, open dialog, verify Direct/Group
tabs, validation messages, group creation, header + list badge, and
search filtering all work.
Out of scope (proposed as follow-ups): group avatar upload, manage
members after creation, leave/mute/archive a group.
Original ask: in the chat page, give users a clear way to create a
group chat (the "+" button already opened a dialog, but it had no name
field, no Direct/Group distinction, and no way to tell groups apart in
the conversation list).
Backend, DB schema, and OpenAPI already supported groups
(isGroup/nameAr/nameEn/participantIds + isAdmin on creator), so this
task was UI-only.
Changes:
- artifacts/teaboy-os/src/pages/chat.tsx — rebuilt the "New
conversation" dialog with:
* Direct / Group segmented tabs (default Direct)
* Bilingual group name fields (AR + EN, at least one required)
* Search-filterable participant list (matches username,
displayNameAr, displayNameEn)
* Custom checkbox UI; direct mode is single-select (clicking
another user replaces selection), group mode multi-select
* Live participant counter and inline validation messages
(pickOnePerson / minTwoMembers / needGroupName)
* Submit button driven by validation; closes on backdrop click
and resets state on open/close
* Switching to Direct mode clears stale group-name input
- Conversation list rows and chat header now show a "Group" / "مجموعة"
badge for is_group conversations; group avatar uses a Users icon
instead of name initials. Direct chats unchanged.
- New convDisplayName helper: prefers active-language name, falls back
to other language, then participant names, then "Direct Message".
- artifacts/teaboy-os/src/locales/{ar,en}.json — added all new strings
under chat.* (modeDirect, modeGroup, groupNameAr/En, placeholders,
searchUsersPlaceholder, participantsCount, validation.*, create,
noUsersFound). Updated EN groupChat label to compact "Group".
No backend, schema, codegen, or OpenAPI changes. Regenerated api
client locally only because pre-existing pages had stale types from
prior merges.
Verification: e2e test passed — login, open dialog, verify Direct/Group
tabs, validation messages, group creation, header + list badge, and
search filtering all work.
Out of scope (proposed as follow-ups): group avatar upload, manage
members after creation, leave/mute/archive a group.
Task #18: self-service "Forgot password?" flow on the sign-in page,
plus an admin-mediated delivery path so tokens actually reach users
without email infrastructure.
Changes:
- New password_reset_tokens table (SHA-256 hashed token, 1h TTL,
single-use) added via schema + raw SQL.
- Public endpoints: POST /auth/forgot-password (identical response
for valid/invalid identifiers, no account enumeration),
POST /auth/reset-password/verify, POST /auth/reset-password.
Raw tokens are never returned or logged — only id + expiry.
- Admin-only endpoint: POST /auth/admin/users/:id/issue-reset-link
returns a one-time reset URL (origin + hex token) so admins can
share it with the user out-of-band until email delivery lands.
- Frontend: "Forgot password?" link on login, new /forgot-password
and /reset-password pages, admin Users list gets a KeyRound button
opening a modal with the generated URL and a Copy button. Public
routes /forgot-password and /reset-password registered in
AuthContext.
- Bilingual EN/AR copy for all new screens and admin modal.
Verification: full end-to-end test passed — admin generated link,
user reset password via link, logged in with new password, and
reused token was rejected as invalid (single-use enforced).
Follow-ups proposed: #25 transactional email delivery, #26 rate
limiting on the public reset endpoints.
Task #18: self-service "Forgot password?" flow on the sign-in page,
plus an admin-mediated delivery path so tokens actually reach users
without email infrastructure.
Changes:
- New password_reset_tokens table (SHA-256 hashed token, 1h TTL,
single-use) added via schema + raw SQL.
- Public endpoints: POST /auth/forgot-password (identical response
for valid/invalid identifiers, no account enumeration),
POST /auth/reset-password/verify, POST /auth/reset-password.
Raw tokens are never returned or logged — only id + expiry.
- Admin-only endpoint: POST /auth/admin/users/:id/issue-reset-link
returns a one-time reset URL (origin + hex token) so admins can
share it with the user out-of-band until email delivery lands.
- Frontend: "Forgot password?" link on login, new /forgot-password
and /reset-password pages, admin Users list gets a KeyRound button
opening a modal with the generated URL and a Copy button. Public
routes /forgot-password and /reset-password registered in
AuthContext.
- Bilingual EN/AR copy for all new screens and admin modal.
Verification: full end-to-end test passed — admin generated link,
user reset password via link, logged in with new password, and
reused token was rejected as invalid (single-use enforced).
Follow-ups proposed: #25 transactional email delivery, #26 rate
limiting on the public reset endpoints.
Replit-Task-Id: e7628acb-8901-4b62-a7ee-a1149d9e993f
Original task: Add a 7d/30d/90d range selector to the admin dashboard
trend cards/chart and have /stats/admin accept a matching range parameter.
Changes:
- OpenAPI (lib/api-spec/openapi.yaml): added optional `range` query
parameter (enum 7d|30d|90d, default 7d) to GET /stats/admin and
refactored AdminStats fields to be range-agnostic — added `range`
and `rangeDays`, renamed `*Last7Days`/`*Prev7Days` to `*InRange`/
`*PrevRange`. Regenerated api-client-react and api-zod.
- API server (artifacts/api-server/src/routes/stats.ts): parses and
validates the `range` param, computes range/prev-range windows
generically, and returns daily series sized to rangeDays.
- Admin UI (artifacts/teaboy-os/src/pages/admin.tsx): adds a segmented
range selector at the top of the dashboard, passes the selected
range into the stats query (with proper queryKey), and adapts the
trend chart for denser ranges (skipped per-bar count text >14 days,
every-Nth date label, month/day formatting for 30d/90d).
- Locale files (en.json/ar.json): added range/prevRange labels,
trends/rangeSelector strings, and *Ranged variants of summary keys.
Old keys kept to avoid stale references.
Verification:
- pnpm typecheck passes across libs and artifacts.
- e2e test (login as admin → toggle 7d/30d/90d → verify chart bar
counts and aria-pressed state, plus API responses for each range)
passes.
Notes / deviations:
- Had to (re)create the `app_opens` table in the dev DB and reset the
seeded admin password hash to run the e2e test; both were preexisting
environment drift unrelated to this task.
- Followed up with: custom date range, persisting last-used range.
Original task: Add a 7d/30d/90d range selector to the admin dashboard
trend cards/chart and have /stats/admin accept a matching range parameter.
Changes:
- OpenAPI (lib/api-spec/openapi.yaml): added optional `range` query
parameter (enum 7d|30d|90d, default 7d) to GET /stats/admin and
refactored AdminStats fields to be range-agnostic — added `range`
and `rangeDays`, renamed `*Last7Days`/`*Prev7Days` to `*InRange`/
`*PrevRange`. Regenerated api-client-react and api-zod.
- API server (artifacts/api-server/src/routes/stats.ts): parses and
validates the `range` param, computes range/prev-range windows
generically, and returns daily series sized to rangeDays.
- Admin UI (artifacts/teaboy-os/src/pages/admin.tsx): adds a segmented
range selector at the top of the dashboard, passes the selected
range into the stats query (with proper queryKey), and adapts the
trend chart for denser ranges (skipped per-bar count text >14 days,
every-Nth date label, month/day formatting for 30d/90d).
- Locale files (en.json/ar.json): added range/prevRange labels,
trends/rangeSelector strings, and *Ranged variants of summary keys.
Old keys kept to avoid stale references.
Verification:
- pnpm typecheck passes across libs and artifacts.
- e2e test (login as admin → toggle 7d/30d/90d → verify chart bar
counts and aria-pressed state, plus API responses for each range)
passes.
Notes / deviations:
- Had to (re)create the `app_opens` table in the dev DB and reset the
seeded admin password hash to run the e2e test; both were preexisting
environment drift unrelated to this task.
- Followed up with: custom date range, persisting last-used range.
Replit-Task-Id: 8066030b-b5c4-4b5c-a630-65616df5449e
Update the services page layout to display smaller service cards with a square aspect ratio and increased column count, along with adjustments to padding, image sizes, and text truncation for a more compact design.
Update the services page layout to display smaller service cards with a square aspect ratio and increased column count, along with adjustments to padding, image sizes, and text truncation for a more compact design.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b2994de5-2015-4dec-9ec2-f273f1b2b8e7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/pCDTgLS
Replit-Helium-Checkpoint-Created: true
- New `app_opens` table (id, user_id, app_id, created_at) and Drizzle
schema; exported from lib/db schema index.
- New `POST /api/apps/{id}/open` endpoint logs an open for the current
user (auth required, 204 on success, 404 for unknown app id).
- Extended `GET /api/stats/admin` with appOpensByDay/appOpensLast7Days/
appOpensPrev7Days and servicesCreatedByDay/servicesCreatedLast7Days,
refactored around a shared buildSeries helper.
- Regenerated api-client/zod and added two new bar charts (App opens,
Services added) to the admin Dashboard alongside the existing
Sign-ups chart, with EN/AR translations.
- Home page fires bare `logAppOpen(id, { keepalive: true })` before
navigating so the request survives client-side route changes; the
bottom dock buttons also use this helper.
- Restructured SortableAppIcon so the click target and dnd-kit
listeners live on the same <button>, fixing a click-vs-drag
interaction that prevented the open event from firing in tests.
Rebase notes:
- home.tsx: incoming main reworked AppIcon styling, the apps grid
header (with count and empty state), and the dock button. Kept all
incoming visual changes while preserving this task's
AppIconContent fragment, single-button SortableAppIcon, and
openApp() wiring (so dock + grid both log opens).
- opengraph.jpg: kept incoming binary version.
E2E verified: clicking app icons increments app_opens; admin dashboard
renders all three charts.
- New `app_opens` table (id, user_id, app_id, created_at) and Drizzle
schema; exported from lib/db schema index.
- New `POST /api/apps/{id}/open` endpoint logs an open for the current
user (auth required, 204 on success, 404 for unknown app id).
- Extended `GET /api/stats/admin` with appOpensByDay/appOpensLast7Days/
appOpensPrev7Days and servicesCreatedByDay/servicesCreatedLast7Days,
refactored around a shared buildSeries helper.
- Regenerated api-client/zod and added two new bar charts (App opens,
Services added) to the admin Dashboard alongside the existing
Sign-ups chart, with EN/AR translations.
- Home page fires bare `logAppOpen(id, { keepalive: true })` before
navigating so the request survives client-side route changes; the
bottom dock buttons also use this helper.
- Restructured SortableAppIcon so the click target and dnd-kit
listeners live on the same <button>, fixing a click-vs-drag
interaction that prevented the open event from firing in tests.
Rebase notes:
- home.tsx: incoming main reworked AppIcon styling, the apps grid
header (with count and empty state), and the dock button. Kept all
incoming visual changes while preserving this task's
AppIconContent fragment, single-button SortableAppIcon, and
openApp() wiring (so dock + grid both log opens).
- opengraph.jpg: kept incoming binary version.
E2E verified: clicking app icons increments app_opens; admin dashboard
renders all three charts.
Replit-Task-Id: 776c14f7-4e5a-4bf6-80e2-a6c7586c1fcb
Modify `chart.tsx` to import and utilize `useTranslation` and `formatNumber` for locale-aware tooltip value formatting, replacing hardcoded US English number formatting.
Modify `chart.tsx` to import and utilize `useTranslation` and `formatNumber` for locale-aware tooltip value formatting, replacing hardcoded US English number formatting.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4cdc0cd7-7821-4410-a349-dc57c6975820
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/WWIlFT7
Replit-Helium-Checkpoint-Created: true
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
while month/weekday names stay localized. Exports formatTime,
formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
and a greetingKey() helper for time-of-day greetings.
- Replaced every toLocaleTimeString / toLocaleDateString /
toLocaleString / direct Intl.DateTimeFormat call in home, admin,
chat and notifications with the new helpers. No remaining
Arabic-Indic digits anywhere in the UI (verified e2e).
- Redesigned the home screen:
* Tighter status bar with three balanced sections: identity (avatar
+ name + admin tag), centered clock + Hijri/Gregorian dates,
grouped controls (language, bell, logout).
* Personal greeting line ("Good morning / صباح الخير …") driven by
local hour and the user's display name.
* Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
with soft glass + small colored icon tiles.
* Polished apps grid with section count badge and a friendly empty
state. Drag-and-drop reorder behavior preserved.
* Type-safe Lucide icon resolver (no unsafe casts).
- Added i18n keys home.greeting.{morning,afternoon,evening},
home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.
- Cleaned up obsolete follow-up plan file now that this work shipped.
Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
while month/weekday names stay localized. Exports formatTime,
formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
and a greetingKey() helper for time-of-day greetings.
- Replaced every toLocaleTimeString / toLocaleDateString /
toLocaleString / direct Intl.DateTimeFormat call in home, admin,
chat and notifications with the new helpers. No remaining
Arabic-Indic digits anywhere in the UI (verified e2e).
- Redesigned the home screen:
* Tighter status bar with three balanced sections: identity (avatar
+ name + admin tag), centered clock + Hijri/Gregorian dates,
grouped controls (language, bell, logout).
* Personal greeting line ("Good morning / صباح الخير …") driven by
local hour and the user's display name.
* Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
with soft glass + small colored icon tiles.
* Polished apps grid with section count badge and a friendly empty
state. Drag-and-drop reorder behavior preserved.
* Type-safe Lucide icon resolver (no unsafe casts).
- Added i18n keys home.greeting.{morning,afternoon,evening},
home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.
- Cleaned up obsolete follow-up plan file now that this work shipped.
Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
while month/weekday names stay localized. Exports formatTime,
formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
and a greetingKey() helper for time-of-day greetings.
- Replaced every toLocaleTimeString / toLocaleDateString /
toLocaleString / direct Intl.DateTimeFormat call in home, admin,
chat and notifications with the new helpers. No remaining
Arabic-Indic digits anywhere in the UI (verified e2e).
- Redesigned the home screen:
* Tighter status bar with three balanced sections: identity (avatar
+ name + admin tag), centered clock + Hijri/Gregorian dates,
grouped controls (language, bell, logout).
* Personal greeting line ("Good morning / صباح الخير …") driven by
local hour and the user's display name.
* Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
with soft glass + small colored icon tiles.
* Polished apps grid with section count badge and a friendly empty
state. Drag-and-drop reorder behavior preserved.
* Type-safe Lucide icon resolver (no unsafe casts).
- Added i18n keys home.greeting.{morning,afternoon,evening},
home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.
- Cleaned up obsolete follow-up plan file now that this work shipped.
Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
while month/weekday names stay localized. Exports formatTime,
formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
and a greetingKey() helper for time-of-day greetings.
- Replaced every toLocaleTimeString / toLocaleDateString /
toLocaleString / direct Intl.DateTimeFormat call in home, admin,
chat and notifications with the new helpers. No remaining
Arabic-Indic digits anywhere in the UI (verified e2e).
- Redesigned the home screen:
* Tighter status bar with three balanced sections: identity (avatar
+ name + admin tag), centered clock + Hijri/Gregorian dates,
grouped controls (language, bell, logout).
* Personal greeting line ("Good morning / صباح الخير …") driven by
local hour and the user's display name.
* Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
with soft glass + small colored icon tiles.
* Polished apps grid with section count badge and a friendly empty
state. Drag-and-drop reorder behavior preserved.
* Type-safe Lucide icon resolver (no unsafe casts).
- Added i18n keys home.greeting.{morning,afternoon,evening},
home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.
- Cleaned up obsolete follow-up plan file now that this work shipped.
Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
while month/weekday names stay localized. Exports formatTime,
formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
and a greetingKey() helper for time-of-day greetings.
- Replaced every toLocaleTimeString / toLocaleDateString /
toLocaleString / direct Intl.DateTimeFormat call in home, admin,
chat and notifications with the new helpers. No remaining
Arabic-Indic digits anywhere in the UI (verified e2e).
- Redesigned the home screen:
* Tighter status bar with three balanced sections: identity (avatar
+ name + admin tag), centered clock + Hijri/Gregorian dates,
grouped controls (language, bell, logout).
* Personal greeting line ("Good morning / صباح الخير …") driven by
local hour and the user's display name.
* Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
with soft glass + small colored icon tiles.
* Polished apps grid with section count badge and a friendly empty
state. Drag-and-drop reorder behavior preserved.
* Type-safe Lucide icon resolver (no unsafe casts).
- Added i18n keys home.greeting.{morning,afternoon,evening},
home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.
- Cleaned up obsolete follow-up plan file now that this work shipped.
Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
while month/weekday names stay localized. Exports formatTime,
formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
and a greetingKey() helper for time-of-day greetings.
- Replaced every toLocaleTimeString / toLocaleDateString /
toLocaleString / direct Intl.DateTimeFormat call in home, admin,
chat and notifications with the new helpers. No remaining
Arabic-Indic digits anywhere in the UI (verified e2e).
- Redesigned the home screen:
* Tighter status bar with three balanced sections: identity (avatar
+ name + admin tag), centered clock + Hijri/Gregorian dates,
grouped controls (language, bell, logout).
* Personal greeting line ("Good morning / صباح الخير …") driven by
local hour and the user's display name.
* Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
with soft glass + small colored icon tiles.
* Polished apps grid with section count badge and a friendly empty
state. Drag-and-drop reorder behavior preserved.
* Type-safe Lucide icon resolver (no unsafe casts).
- Added i18n keys home.greeting.{morning,afternoon,evening},
home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.
- Cleaned up obsolete follow-up plan file now that this work shipped.
Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
Add editable footer text fields to site settings, update OpenAPI schema and API client, refactor the login page to display AI-generated artwork and use dynamic footer text, and remove the old logo and welcome heading.
Add editable footer text fields to site settings, update OpenAPI schema and API client, refactor the login page to display AI-generated artwork and use dynamic footer text, and remove the old logo and welcome heading.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a650b496-7c3c-427e-bfbe-77bc8b9b5dd2
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/gdgNDb6
Replit-Helium-Checkpoint-Created: true