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.
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.
- 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.
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.
Implements drag-and-drop functionality for reordering apps using @dnd-kit, adds a new `userAppOrdersTable` to the database schema to store user-specific app order preferences, and introduces a new API endpoint `/api/me/app-order` for updating these preferences.
User wants the admin to be able to open or close public self-registration
from inside the app instead of removing the registration page entirely.
Changes:
- Added registration_open boolean column to app_settings (default true)
- Exposed registrationOpen in AppSettings + UpdateAppSettingsBody schemas
in openapi.yaml; regenerated client + zod
- Backend: POST /api/auth/register now returns 403 "Registration is
closed" when the flag is off (checks settings row before any other work)
- Admin Site Settings panel now has a switch to toggle public
registration on/off, with bilingual label + helper text
- Login page hides the "Create account" link when registration is closed
- Register page short-circuits to a friendly "registration closed"
card with a Back to Login button when the flag is off (and redirects
if the user lands there directly)
- Added bilingual locale keys: registrationOpen, registrationOpenHint,
registrationClosed
Verified: GET /api/settings returns the new field; POST /api/auth/register
returns 403 when closed and proceeds normally when open. Both typecheck
suites pass.
User asked to remove the hardcoded "TeaBoy" branding and let admins
change the system name. Also completes the in-progress service image
upload work via App Storage.
Changes:
- New app_settings table (single row id=1) with siteNameAr/siteNameEn
- New /settings endpoints: GET (public) + PATCH (admin)
- Atomic ensureSettingsRow via INSERT ... ON CONFLICT DO NOTHING
to avoid race conditions
- New SiteSettingsPanel tab in admin page (Arabic + English inputs)
- New useAppName hook reads settings, updates document.title, falls
back to defaults while loading
- Login + register pages now display the dynamic site name
- Service image upload (App Storage) wired via useUpload + presigned
GCS URL flow; admin component ServiceImageUploader
- Storage routes: /storage/uploads/request-url and /storage/objects/*
now require auth (closes previously-open endpoints flagged by review)
- Added AppSettings/UpdateAppSettingsBody + storage schemas to
openapi.yaml; regenerated client and zod
- Exposed UploadResponse from @workspace/object-storage-web; added
composite:true so it can be referenced by teaboy-os tsconfig
Validation: typechecks pass for api-server and teaboy-os; settings GET
returns row; upload URL endpoint returns 401 without auth.
Completed full-stack TeaBoy OS build:
Backend (artifacts/api-server):
- Session-based auth with express-session + connect-pg-simple (PostgreSQL sessions)
- RBAC middleware (requireAuth, requireAdmin) with role-based guards
- REST routes for: auth (login/logout/register/me/language), apps CRUD, services CRUD,
conversations + messages, notifications, users (admin), home stats
- Socket.IO mounted on same HTTP server at /api/socket.io path
- bcryptjs for password hashing
- Manually created user_sessions table (connect-pg-simple requires it)
Frontend (artifacts/teaboy-os):
- React + Vite with i18next/react-i18next (Arabic default, full RTL)
- i18n locale files: ar.json + en.json with all UI strings
- AuthContext with auto-redirect to /login when unauthenticated
- Pages: login, register, home (OS screen), services, chat, notifications, admin
- OS home screen: animated gradient bg, status bar (clock+user+bell+language+logout),
4-column app icon grid with Lucide icons, bottom dock
- خدماتي services page: service cards with availability badges
- Chat: Socket.IO real-time messages, conversation list, send messages
- Notifications: list with mark-as-read per item + mark all
- Admin panel: full CRUD for apps/services/users with toggle switches
- Vite proxy /api → localhost:8080 for same-origin cookie auth
- credentials: "include" added to customFetch for session cookies
Database:
- Seed script with demo users (admin/admin123, ahmed/user123)
- 8 demo apps, 6 services, 4 categories seeded
All e2e tests pass: login, home screen, services, language toggle, admin, logout