diff --git a/artifacts/api-server/src/app.ts b/artifacts/api-server/src/app.ts
index 585bdb50..e3b44841 100644
--- a/artifacts/api-server/src/app.ts
+++ b/artifacts/api-server/src/app.ts
@@ -64,7 +64,7 @@ export const sessionMiddleware = session({
if (process.env.NODE_ENV === "production") {
throw new Error("SESSION_SECRET must be set in production");
}
- return "teaboy-dev-secret-change-before-deploy";
+ return "tx-dev-secret-change-before-deploy";
})(),
resave: false,
saveUninitialized: false,
diff --git a/artifacts/api-server/tests/groups-crud.test.mjs b/artifacts/api-server/tests/groups-crud.test.mjs
index 468cbeff..72981892 100644
--- a/artifacts/api-server/tests/groups-crud.test.mjs
+++ b/artifacts/api-server/tests/groups-crud.test.mjs
@@ -114,7 +114,7 @@ test("GET /api/groups requires admin (regular user gets 403)", async () => {
assert.equal(res.status, 403);
});
-test("admin lists default seed groups including Admins, TeaBoy, Everyone", async () => {
+test("admin lists default seed groups including Admins, Tx, Everyone", async () => {
const res = await fetch(`${API_BASE}/api/groups`, {
headers: { Cookie: adminCookie },
});
@@ -122,7 +122,7 @@ test("admin lists default seed groups including Admins, TeaBoy, Everyone", async
const list = await res.json();
const names = new Set(list.map((g) => g.name));
assert.ok(names.has("Admins"));
- assert.ok(names.has("TeaBoy"));
+ assert.ok(names.has("Tx"));
assert.ok(names.has("Everyone"));
});
diff --git a/artifacts/teaboy-os/index.html b/artifacts/teaboy-os/index.html
index 9ccce42f..9d9953a7 100644
--- a/artifacts/teaboy-os/index.html
+++ b/artifacts/teaboy-os/index.html
@@ -3,7 +3,7 @@
- TeaBoy OS
+ Tx OS
diff --git a/artifacts/teaboy-os/src/components/clock.tsx b/artifacts/teaboy-os/src/components/clock.tsx
index 02f0729e..fc6a09ae 100644
--- a/artifacts/teaboy-os/src/components/clock.tsx
+++ b/artifacts/teaboy-os/src/components/clock.tsx
@@ -339,8 +339,8 @@ export function AnalogClockWidget({
);
}
-const HOME_CLOCK_STORAGE_KEY = "teaboy:home-clock-visible";
-const HOME_CLOCK_EVENT = "teaboy:home-clock-changed";
+const HOME_CLOCK_STORAGE_KEY = "tx:home-clock-visible";
+const HOME_CLOCK_EVENT = "tx:home-clock-changed";
function readHomeClockVisible(): boolean {
if (typeof window === "undefined") return true;
@@ -384,8 +384,8 @@ export function useHomeClockVisibility(): [boolean, (next: boolean) => void] {
return [visible, setVisible];
}
-const TOPBAR_CLOCK_STORAGE_KEY = "teaboy:topbar-clock-visible";
-const TOPBAR_CLOCK_EVENT = "teaboy:topbar-clock-changed";
+const TOPBAR_CLOCK_STORAGE_KEY = "tx:topbar-clock-visible";
+const TOPBAR_CLOCK_EVENT = "tx:topbar-clock-changed";
function readTopbarClockVisible(): boolean {
if (typeof window === "undefined") return true;
@@ -428,13 +428,13 @@ export function useTopbarClockVisibility(): [boolean, (next: boolean) => void] {
return [visible, setVisible];
}
-const HOME_CLOCK_SIZE_EVENT = "teaboy:home-clock-size-changed";
+const HOME_CLOCK_SIZE_EVENT = "tx:home-clock-size-changed";
export type HomeClockSize = "compact" | "large";
function homeClockSizeKey(userId: number | string | null | undefined): string {
return userId == null
- ? "teaboy:home-clock-size"
- : `teaboy:home-clock-size:${userId}`;
+ ? "tx:home-clock-size"
+ : `tx:home-clock-size:${userId}`;
}
function readHomeClockSize(userId: number | string | null | undefined): HomeClockSize {
@@ -473,12 +473,12 @@ export function useHomeClockSize(
return [size, setSize];
}
-const HOME_CLOCK_POS_EVENT = "teaboy:home-clock-pos-changed";
+const HOME_CLOCK_POS_EVENT = "tx:home-clock-pos-changed";
function homeClockPosKey(userId: number | string | null | undefined): string {
return userId == null
- ? "teaboy:home-clock-position"
- : `teaboy:home-clock-position:${userId}`;
+ ? "tx:home-clock-position"
+ : `tx:home-clock-position:${userId}`;
}
function readHomeClockPosition(userId: number | string | null | undefined): number {
diff --git a/artifacts/teaboy-os/src/hooks/use-app-settings.ts b/artifacts/teaboy-os/src/hooks/use-app-settings.ts
index 970c39cd..23d66243 100644
--- a/artifacts/teaboy-os/src/hooks/use-app-settings.ts
+++ b/artifacts/teaboy-os/src/hooks/use-app-settings.ts
@@ -11,7 +11,7 @@ export function useAppName() {
const { i18n } = useTranslation();
const { data } = useAppSettings();
const isAr = i18n.language === "ar";
- const fallback = isAr ? "نظام TeaBoy" : "TeaBoy OS";
+ const fallback = isAr ? "نظام Tx" : "Tx OS";
const name = data ? (isAr ? data.siteNameAr : data.siteNameEn) : fallback;
useEffect(() => {
if (typeof document !== "undefined" && data) {
diff --git a/artifacts/teaboy-os/src/i18n.ts b/artifacts/teaboy-os/src/i18n.ts
index 2e1459c3..812cef6b 100644
--- a/artifacts/teaboy-os/src/i18n.ts
+++ b/artifacts/teaboy-os/src/i18n.ts
@@ -4,7 +4,7 @@ import ar from './locales/ar.json';
import en from './locales/en.json';
// Initialize i18next
-const savedLanguage = localStorage.getItem('teaboy-lang') || 'ar';
+const savedLanguage = localStorage.getItem('tx-lang') || 'ar';
i18n
.use(initReactI18next)
@@ -26,7 +26,7 @@ document.documentElement.lang = savedLanguage;
i18n.on('languageChanged', (lng) => {
document.documentElement.dir = lng === 'ar' ? 'rtl' : 'ltr';
document.documentElement.lang = lng;
- localStorage.setItem('teaboy-lang', lng);
+ localStorage.setItem('tx-lang', lng);
});
export default i18n;
diff --git a/artifacts/teaboy-os/src/locales/ar.json b/artifacts/teaboy-os/src/locales/ar.json
index 3aa96719..d071bac3 100644
--- a/artifacts/teaboy-os/src/locales/ar.json
+++ b/artifacts/teaboy-os/src/locales/ar.json
@@ -488,7 +488,7 @@
"error": "حدث خطأ",
"success": "تمت العملية بنجاح",
"language": "English",
- "appName": "نظام TeaBoy"
+ "appName": "نظام Tx"
},
"notes": {
"title": "الملاحظات",
diff --git a/artifacts/teaboy-os/src/locales/en.json b/artifacts/teaboy-os/src/locales/en.json
index 30472b68..57789296 100644
--- a/artifacts/teaboy-os/src/locales/en.json
+++ b/artifacts/teaboy-os/src/locales/en.json
@@ -485,7 +485,7 @@
"error": "An error occurred",
"success": "Success",
"language": "العربية",
- "appName": "TeaBoy OS"
+ "appName": "Tx OS"
},
"notes": {
"title": "Notes",
diff --git a/artifacts/teaboy-os/src/pages/chat.tsx b/artifacts/teaboy-os/src/pages/chat.tsx
index ee570e9d..6c12d097 100644
--- a/artifacts/teaboy-os/src/pages/chat.tsx
+++ b/artifacts/teaboy-os/src/pages/chat.tsx
@@ -253,7 +253,7 @@ export default function ChatPage() {
// arrive in the conversation before the user next opens chat.
useEffect(() => {
if (!user || !conversations) return;
- const ackKey = `teaboy:admin-known:${user.id}`;
+ const ackKey = `tx:admin-known:${user.id}`;
let ack: number[] = [];
try {
ack = JSON.parse(localStorage.getItem(ackKey) ?? "[]");
diff --git a/artifacts/teaboy-os/tests/home-clock-persistence.spec.mjs b/artifacts/teaboy-os/tests/home-clock-persistence.spec.mjs
index f0941023..44cace59 100644
--- a/artifacts/teaboy-os/tests/home-clock-persistence.spec.mjs
+++ b/artifacts/teaboy-os/tests/home-clock-persistence.spec.mjs
@@ -1,7 +1,7 @@
// UI test verifying that the home-page clock widget keeps its grid position
// and compact/large size after a full page reload, and that the changes are
// persisted to localStorage under the documented keys
-// `teaboy:home-clock-position` / `teaboy:home-clock-size` (or their per-user
+// `tx:home-clock-position` / `tx:home-clock-size` (or their per-user
// variants once a user is logged in).
//
// This is the recurring automated counterpart to the manual e2e check
@@ -95,9 +95,9 @@ async function readClockStorage(page, userId) {
return page.evaluate((uid) => {
return {
position: window.localStorage.getItem(
- `teaboy:home-clock-position:${uid}`,
+ `tx:home-clock-position:${uid}`,
),
- size: window.localStorage.getItem(`teaboy:home-clock-size:${uid}`),
+ size: window.localStorage.getItem(`tx:home-clock-size:${uid}`),
};
}, userId);
}
diff --git a/lib/api-client-react/src/generated/api.schemas.ts b/lib/api-client-react/src/generated/api.schemas.ts
index 5947c93b..a524f7fb 100644
--- a/lib/api-client-react/src/generated/api.schemas.ts
+++ b/lib/api-client-react/src/generated/api.schemas.ts
@@ -2,7 +2,7 @@
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Api
- * TeaBoy OS API specification
+ * Tx OS API specification
* OpenAPI spec version: 0.1.0
*/
export interface HealthStatus {
diff --git a/lib/api-client-react/src/generated/api.ts b/lib/api-client-react/src/generated/api.ts
index 67af3f1a..8e4e2d68 100644
--- a/lib/api-client-react/src/generated/api.ts
+++ b/lib/api-client-react/src/generated/api.ts
@@ -2,7 +2,7 @@
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Api
- * TeaBoy OS API specification
+ * Tx OS API specification
* OpenAPI spec version: 0.1.0
*/
import { useMutation, useQuery } from "@tanstack/react-query";
diff --git a/lib/api-spec/openapi.yaml b/lib/api-spec/openapi.yaml
index cc290e02..9f92faec 100644
--- a/lib/api-spec/openapi.yaml
+++ b/lib/api-spec/openapi.yaml
@@ -3,7 +3,7 @@ info:
# Do not change the title, if the title changes, the import paths will be broken
title: Api
version: 0.1.0
- description: TeaBoy OS API specification
+ description: Tx OS API specification
servers:
- url: /api
description: Base API path
diff --git a/lib/api-zod/src/generated/api.ts b/lib/api-zod/src/generated/api.ts
index 959932c4..2f23f30d 100644
--- a/lib/api-zod/src/generated/api.ts
+++ b/lib/api-zod/src/generated/api.ts
@@ -2,7 +2,7 @@
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Api
- * TeaBoy OS API specification
+ * Tx OS API specification
* OpenAPI spec version: 0.1.0
*/
import * as zod from "zod";
diff --git a/lib/db/src/schema/settings.ts b/lib/db/src/schema/settings.ts
index df51a5f7..e7b43562 100644
--- a/lib/db/src/schema/settings.ts
+++ b/lib/db/src/schema/settings.ts
@@ -4,8 +4,8 @@ import { z } from "zod/v4";
export const appSettingsTable = pgTable("app_settings", {
id: integer("id").primaryKey().default(1),
- siteNameAr: varchar("site_name_ar", { length: 200 }).notNull().default("نظام TeaBoy"),
- siteNameEn: varchar("site_name_en", { length: 200 }).notNull().default("TeaBoy OS"),
+ siteNameAr: varchar("site_name_ar", { length: 200 }).notNull().default("نظام Tx"),
+ siteNameEn: varchar("site_name_en", { length: 200 }).notNull().default("Tx OS"),
registrationOpen: boolean("registration_open").notNull().default(true),
footerTextAr: varchar("footer_text_ar", { length: 300 }).notNull().default("جميع الحقوق محفوظة."),
footerTextEn: varchar("footer_text_en", { length: 300 }).notNull().default("All rights reserved."),
diff --git a/replit.md b/replit.md
index 8f8b4653..7ed2343d 100644
--- a/replit.md
+++ b/replit.md
@@ -1,8 +1,8 @@
-# TeaBoy OS
+# Tx OS
## Overview
-**TeaBoy OS** — a bilingual (Arabic/English, RTL/LTR) full-stack internal web platform styled as an OS-like interface with glassmorphism aesthetics. Built as a pnpm monorepo.
+**Tx OS** — a bilingual (Arabic/English, RTL/LTR) full-stack internal web platform styled as an OS-like interface with glassmorphism aesthetics. Built as a pnpm monorepo.
## Architecture
@@ -11,6 +11,7 @@
├── artifacts/
│ ├── api-server/ Express 5 backend (port 8080)
│ └── teaboy-os/ React + Vite frontend (path: /, port dynamic)
+│ (legacy folder name; product brand is "Tx OS")
├── lib/
│ ├── db/ Drizzle ORM + PostgreSQL schema
│ ├── api-spec/ OpenAPI spec + Orval codegen
@@ -66,3 +67,4 @@
- Socket.IO server path: `/api/socket.io`
- Frontend connects to Socket.IO via same-origin proxy (no separate URL needed)
- i18n locale files: `artifacts/teaboy-os/src/locales/ar.json` and `en.json`
+- Default receivers group is named **Tx** (was renamed from a legacy "TeaBoy"); a one-time migration in the seed script renames any pre-existing legacy group on next run.
diff --git a/scripts/post-merge.sh b/scripts/post-merge.sh
index be2dbc93..0a97617f 100644
--- a/scripts/post-merge.sh
+++ b/scripts/post-merge.sh
@@ -3,6 +3,6 @@ set -e
pnpm install --frozen-lockfile
pnpm --filter db run push-force
# Idempotent: ensure the default groups exist and existing users are mapped
-# (Admins, TeaBoy, Everyone). Safe to run repeatedly.
+# (Admins, Tx, Everyone). Safe to run repeatedly.
pnpm --filter scripts run seed
pnpm --filter @workspace/teaboy-os exec playwright install chromium
diff --git a/scripts/src/seed.ts b/scripts/src/seed.ts
index 66ad0704..a332b56f 100644
--- a/scripts/src/seed.ts
+++ b/scripts/src/seed.ts
@@ -102,7 +102,7 @@ async function main() {
.insert(usersTable)
.values({
username: "admin",
- email: "admin@teaboy.local",
+ email: "admin@tx.local",
passwordHash: adminHash,
displayNameAr: "مدير النظام",
displayNameEn: "System Admin",
@@ -118,7 +118,7 @@ async function main() {
.insert(usersTable)
.values({
username: "ahmed",
- email: "ahmed@teaboy.local",
+ email: "ahmed@tx.local",
passwordHash: userHash,
displayNameAr: "أحمد محمد",
displayNameEn: "Ahmed Mohammed",
@@ -369,7 +369,7 @@ async function main() {
isSystem: 1,
},
{
- name: "TeaBoy",
+ name: "Tx",
descriptionAr: "فريق إعداد وتقديم الطلبات",
descriptionEn: "Team that prepares and delivers orders",
isSystem: 1,
@@ -383,9 +383,21 @@ async function main() {
])
.onConflictDoNothing();
+ // One-time migration: rename legacy "TeaBoy" group to "Tx" if it still exists
+ // and the new "Tx" group hasn't been created yet.
+ const existingGroups = await db.select().from(groupsTable);
+ const legacy = existingGroups.find((g) => g.name === "TeaBoy");
+ const newOne = existingGroups.find((g) => g.name === "Tx");
+ if (legacy && !newOne) {
+ await db
+ .update(groupsTable)
+ .set({ name: "Tx" })
+ .where(eq(groupsTable.id, legacy.id));
+ }
+
const allGroups = await db.select().from(groupsTable);
const adminsGroup = allGroups.find((g) => g.name === "Admins");
- const teaboyGroup = allGroups.find((g) => g.name === "TeaBoy");
+ const txGroup = allGroups.find((g) => g.name === "Tx");
const everyoneGroup = allGroups.find((g) => g.name === "Everyone");
const allRolesNow = await db.select().from(rolesTable);
@@ -399,10 +411,10 @@ async function main() {
.values({ groupId: adminsGroup.id, roleId: adminRoleNow.id })
.onConflictDoNothing();
}
- if (teaboyGroup && orderReceiverNow) {
+ if (txGroup && orderReceiverNow) {
await db
.insert(groupRolesTable)
- .values({ groupId: teaboyGroup.id, roleId: orderReceiverNow.id })
+ .values({ groupId: txGroup.id, roleId: orderReceiverNow.id })
.onConflictDoNothing();
}
@@ -414,13 +426,13 @@ async function main() {
.values(allAppsNow.map((a) => ({ groupId: adminsGroup.id, appId: a.id })))
.onConflictDoNothing();
}
- // TeaBoy gets the services app (which is where receivers see incoming orders)
- if (teaboyGroup) {
+ // The Tx receivers group gets the services app (where receivers see incoming orders)
+ if (txGroup) {
const servicesApp = allAppsNow.find((a) => a.slug === "services");
if (servicesApp) {
await db
.insert(groupAppsTable)
- .values({ groupId: teaboyGroup.id, appId: servicesApp.id })
+ .values({ groupId: txGroup.id, appId: servicesApp.id })
.onConflictDoNothing();
}
}
@@ -447,7 +459,7 @@ async function main() {
}
}
- if (teaboyGroup && orderReceiverNow) {
+ if (txGroup && orderReceiverNow) {
const receiverRows = await db
.select({ userId: userRolesTable.userId })
.from(userRolesTable)
@@ -455,7 +467,7 @@ async function main() {
if (receiverRows.length > 0) {
await db
.insert(userGroupsTable)
- .values(receiverRows.map((r) => ({ userId: r.userId, groupId: teaboyGroup.id })))
+ .values(receiverRows.map((r) => ({ userId: r.userId, groupId: txGroup.id })))
.onConflictDoNothing();
}
}