From a74550d73fed9b39ffec3de98a2f730de6b92b4e Mon Sep 17 00:00:00 2001 From: jules Date: Tue, 5 May 2026 19:08:36 +1000 Subject: [PATCH] shell+ai: pristine-style nav groups, mobile fixes for /ai Sidenav (app-shell.tsx): - Each NavGroup now carries an icon (Building2 / Database / Plug / MessageSquare / Eye / Sparkles) rendered on the LEFT of the group header, with the chevron moved to the RIGHT. Header typography switched to caption + uppercase + tracking-wider muted, matching pristine-ui's main-branch app-shell. Same change applied to the mobile sheet's group headers. /ai mobile fixes (ai.tsx): - Composer container honors iOS safe-area inset (pb-[max(0.75rem,env(safe-area-inset-bottom))]) so the input clears the home indicator and stays above the soft keyboard. - Composer toolbar wraps on narrow viewports (flex-wrap + gap-y-1) so the agent / model / reasoning / voice chips don't clip. - Empty-state card uses px-4 sm:px-8 instead of hard px-8. - MessageRow's 56px turn-number gutter collapses below sm: prose flows full-width on phone, two-column layout returns at sm+. /ai desktop centering: - Console wrapper opts out of AppShell's [&>*:first-child]:lg:pr-72 (the page-header clearance for the floating top-right pill) via lg:!pr-0. The /ai surface has no top-right page-header controls, so the inherited padding was shifting the chat column ~144px left of the visible viewport center. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/components/layout/app-shell.tsx | 41 ++++++++++++++++++++--------- app/routes/ai.tsx | 20 +++++++------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/app/components/layout/app-shell.tsx b/app/components/layout/app-shell.tsx index b25fa1a..d6c440d 100644 --- a/app/components/layout/app-shell.tsx +++ b/app/components/layout/app-shell.tsx @@ -33,7 +33,11 @@ import { Megaphone, AlertOctagon, SearchCode, - ChevronRight, + ChevronDown, + Database, + Plug, + MessageSquare, + Eye, // CREMA:NAV-ICONS } from "lucide-react" @@ -102,6 +106,7 @@ type NavItem = { type NavGroup = { key: string label: string + icon: React.ComponentType<{ className?: string }> items: NavItem[] } @@ -119,6 +124,7 @@ const navGroups: NavGroup[] = [ { key: "tenancy", label: "Tenancy", + icon: Building2, items: [ { to: "/tenants", icon: Building2, label: "Tenants" }, { to: "/memberships", icon: UserCheck, label: "Memberships" }, @@ -129,6 +135,7 @@ const navGroups: NavGroup[] = [ { key: "data", label: "Data", + icon: Database, items: [ { to: "/storage", icon: HardDrive, label: "Storage" }, { to: "/buckets", icon: Boxes, label: "Buckets" }, @@ -138,6 +145,7 @@ const navGroups: NavGroup[] = [ { key: "integrations", label: "Integrations", + icon: Plug, items: [ { to: "/webhooks", icon: WebhookIcon, label: "Webhooks" }, { to: "/scheduled-tasks", icon: CalendarClock, label: "Scheduled" }, @@ -147,6 +155,7 @@ const navGroups: NavGroup[] = [ { key: "comms", label: "Communications", + icon: MessageSquare, items: [ { to: "/announcements", icon: Megaphone, label: "Announcements" }, { to: "/status-page", icon: AlertOctagon, label: "Status page" }, @@ -155,6 +164,7 @@ const navGroups: NavGroup[] = [ { key: "observability", label: "Observability", + icon: Eye, items: [ { to: "/monitoring", icon: Gauge, label: "Monitoring" }, { to: "/activity", icon: Activity, label: "Audit log" }, @@ -163,6 +173,7 @@ const navGroups: NavGroup[] = [ { key: "ai", label: "AI & Search", + icon: Sparkles, items: [ { to: "/ai", icon: Bot, label: "AI" }, { to: "/search", icon: SearchCode, label: "Search" }, @@ -332,22 +343,24 @@ export function AppShell({ {navGroups.map((group) => { const isOpen = !!openGroups[group.key] + const GroupIcon = group.icon return ( -
+
{isOpen ? (
@@ -440,22 +453,24 @@ export function AppShell({ {navGroups.map((group) => { const isOpen = !!openGroups[group.key] + const GroupIcon = group.icon return ( -
+
{isOpen ? (
diff --git a/app/routes/ai.tsx b/app/routes/ai.tsx index a2a747e..5c68152 100644 --- a/app/routes/ai.tsx +++ b/app/routes/ai.tsx @@ -630,7 +630,7 @@ export default function AIRoute() { * toggle still works for them). */}
@@ -1363,7 +1363,7 @@ function ChatSurface({ * center when empty, then springs to sticky-bottom on the first message. */}
-
+
+
T{(turnNum ?? 0).toString().padStart(2, "0")} @@ -1641,7 +1641,7 @@ function MessageRow({ ) : null}
-
+
›  {content} @@ -1655,8 +1655,8 @@ function MessageRow({ // there's no prose (just tool calls), suppress the row entirely. if (!content.trim()) return null return ( -
-
+
+
T{(turnNum ?? 0).toString().padStart(2, "0")} @@ -1664,7 +1664,7 @@ function MessageRow({ {agentName?.slice(0, 6).toLowerCase() ?? "atlas"}
-
+
@@ -1781,7 +1781,7 @@ function Composer({ className="min-h-[3.5rem] w-full resize-none bg-transparent outline-none" />
-
+