+
)
}
diff --git a/apps/builder/src/app/space/[workspaceId]/layout.tsx b/apps/builder/src/app/space/[workspaceId]/layout.tsx
index e65f214b4..fbfb76823 100644
--- a/apps/builder/src/app/space/[workspaceId]/layout.tsx
+++ b/apps/builder/src/app/space/[workspaceId]/layout.tsx
@@ -8,6 +8,7 @@ import {
} from "@chatbotx.io/business"
import {
SidebarInset,
+ SidebarMobileTrigger,
SidebarProvider,
SidebarTrigger,
} from "@chatbotx.io/ui/components/ui/sidebar"
@@ -121,7 +122,20 @@ export default async function WorkspaceLayout({
workspaceId={workspaceId}
/>
-
+ {/*
+ Below `md` the sidebar collapses into a Sheet, and `SidebarTrigger`
+ below is positioned off the inset's inline edge — where nothing can
+ reach it. This bar is the only way in on a phone, so it stays pinned
+ while the page scrolls (the body is the scroll container: the sidebar
+ wrapper is `min-h-svh` with no overflow of its own).
+ */}
+
+
+
+ {targetWorkspaceMember.workspace.name}
+
+
+
-
+
)
diff --git a/apps/builder/src/components/app-tab.tsx b/apps/builder/src/components/app-tab.tsx
index d91bf481b..cfb00e167 100644
--- a/apps/builder/src/components/app-tab.tsx
+++ b/apps/builder/src/components/app-tab.tsx
@@ -20,7 +20,9 @@ type AppTabProps = {
}
function getTabClassName(tab: AppTabProps["tabs"][number]) {
- const base = "border-b-2 py-6 text-sm"
+ // `shrink-0` + `whitespace-nowrap` keep each tab at its natural width so
+ // the strip overflows (and scrolls) instead of squeezing labels.
+ const base = "shrink-0 whitespace-nowrap border-b-2 py-4 text-sm md:py-6"
if (tab.disabled) {
const disabledPresentation =
tab.disabledPresentation === "normal"
@@ -37,7 +39,14 @@ function getTabClassName(tab: AppTabProps["tabs"][number]) {
export function AppTab({ tabs }: AppTabProps) {
return (
-
+ {/*
+ Several surfaces render 5-6 tabs, which cannot fit a phone. Scrolling
+ the strip keeps every tab reachable without pushing the page itself
+ into horizontal overflow. The scrollbar is hidden because the strip
+ sits directly under a card edge, where a persistent bar reads as a
+ rendering artefact; touch scrolling needs no visible track.
+ */}
+
{tabs.map((tab) =>
tab.disabled ? (
diff --git a/apps/builder/src/components/full-bleed.tsx b/apps/builder/src/components/full-bleed.tsx
new file mode 100644
index 000000000..1805dd9c3
--- /dev/null
+++ b/apps/builder/src/components/full-bleed.tsx
@@ -0,0 +1,23 @@
+import { cn } from "@chatbotx.io/ui/lib/utils"
+import type { ReactNode } from "react"
+
+type FullBleedProps = {
+ children: ReactNode
+ className?: string
+}
+
+/**
+ * Cancels the workspace shell's content padding so a page can run edge to edge.
+ *
+ * The shell (`app/space/[workspaceId]/layout.tsx`) pads its `` with
+ * `p-4 md:p-6`. Surfaces that own the whole viewport — the inbox, primarily —
+ * need that padding gone. Doing it inline with a bare negative margin couples
+ * the page to a spacing value it cannot see: change the shell's padding and the
+ * page silently gains or loses a gutter, with nothing to grep for.
+ *
+ * Keeping the mirror here makes the pair greppable and gives it one place to
+ * change. **The margins below must stay the negation of the shell's padding.**
+ */
+export function FullBleed({ children, className }: FullBleedProps) {
+ return