diff --git a/artifacts/tx-os/package.json b/artifacts/tx-os/package.json index ef7f1bc6..da283278 100644 --- a/artifacts/tx-os/package.json +++ b/artifacts/tx-os/package.json @@ -52,6 +52,7 @@ "@types/react-dom": "catalog:", "@vitejs/plugin-react": "catalog:", "@workspace/api-client-react": "workspace:*", + "@workspace/db": "workspace:*", "class-variance-authority": "catalog:", "clsx": "catalog:", "cmdk": "^1.1.1", diff --git a/artifacts/tx-os/public/opengraph.jpg b/artifacts/tx-os/public/opengraph.jpg index c5ec4918..3bdc5e0b 100644 Binary files a/artifacts/tx-os/public/opengraph.jpg and b/artifacts/tx-os/public/opengraph.jpg differ diff --git a/artifacts/tx-os/src/pages/admin.tsx b/artifacts/tx-os/src/pages/admin.tsx index 943e2cea..2acec569 100644 --- a/artifacts/tx-os/src/pages/admin.tsx +++ b/artifacts/tx-os/src/pages/admin.tsx @@ -129,6 +129,7 @@ import { ListUsersStatus } from "@workspace/api-client-react"; import { useQueryClient, useQuery } from "@tanstack/react-query"; import { useAuth } from "@/contexts/AuthContext"; import { useUpload, type UploadResponse } from "@workspace/object-storage-web"; +import { isBuiltinAppSlug } from "@workspace/db/built-in-apps"; import { resolveServiceImageUrl } from "@/lib/image-url"; import { ArrowRight, ArrowLeft, Settings, Plus, Pencil, Trash2, Shield, Upload, Loader2, LayoutDashboard, Grid2X2, Coffee, Users as UsersIcon, Menu as MenuIcon, TrendingUp, TrendingDown, UserPlus, Activity, KeyRound, Copy, ChevronDown, ScrollText, Download, X, ImageIcon, Power, HelpCircle, Palette, Link2, Hash, Lock, ExternalLink, type LucideIcon } from "lucide-react"; import * as LucideIcons from "lucide-react"; @@ -1238,25 +1239,8 @@ export default function AdminPage() { const previewColor = /^#[0-9a-fA-F]{6}$/.test(editingApp.form.color) ? editingApp.form.color : "#6366f1"; - // Mirror of BUILTIN_APP_SLUGS in @workspace/db (see - // lib/db/src/built-in-apps.ts). Their `route` is hardcoded in - // App.tsx, so the API rejects route changes for these slugs. - // Duplicated here because @workspace/db pulls in `pg` which - // can't run in the browser; kept tiny and pinned next to the - // server constant in code review. - const BUILTIN_APP_SLUGS_FE = [ - "services", - "notifications", - "admin", - "notes", - "my-orders", - "executive-meetings", - ] as const; const isBuiltin = - editingApp.id !== undefined && - (BUILTIN_APP_SLUGS_FE as readonly string[]).includes( - editingApp.form.slug, - ); + editingApp.id !== undefined && isBuiltinAppSlug(editingApp.form.slug); const isExternal = editingApp.form.openMode !== "internal"; return (
-
- - setEditingApp({ ...editingApp, form: { ...editingApp.form, route: e.target.value } })} - readOnly={isBuiltin || isExternal} - disabled={isExternal} - className={cn( - "bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm font-mono focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400", - (isBuiltin || isExternal) && "opacity-70 cursor-not-allowed", - )} - dir="ltr" - placeholder="/notes" - /> -

- {isBuiltin - ? t("admin.builtinPathLocked") - : isExternal - ? (lang === "ar" ? "غير مستخدم لتطبيقات الروابط الخارجية" : "Not used for external-link apps") + {!isExternal && ( +

+ + setEditingApp({ ...editingApp, form: { ...editingApp.form, route: e.target.value } })} + readOnly={isBuiltin} + className={cn( + "bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm font-mono focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400", + isBuiltin && "opacity-70 cursor-not-allowed", + )} + dir="ltr" + placeholder="/notes" + /> +

+ {isBuiltin + ? t("admin.builtinPathLocked") : (lang === "ar" ? "المسار داخل التطبيق (مثل /notes)" : "URL path inside the app (e.g. /notes)")} -

-
+

+
+ )} {/* App image (optional, replaces the Lucide icon on the launcher) */} diff --git a/artifacts/tx-os/tests/admin-app-image-external-embedded.spec.mjs b/artifacts/tx-os/tests/admin-app-image-external-embedded.spec.mjs new file mode 100644 index 00000000..f333cbfe --- /dev/null +++ b/artifacts/tx-os/tests/admin-app-image-external-embedded.spec.mjs @@ -0,0 +1,153 @@ +// Task #517 E2E: +// 1) An app with an `image_url` renders that image (not a Lucide icon) +// on the launcher tile. +// 2) An app with `open_mode='external_tab'` opens its `external_url` in +// a new browser tab when the launcher tile is clicked (we intercept +// window.open). +// 3) An app with `open_mode='external_iframe'` navigates the SPA to +// `/embedded/:id`, which renders an