agent-shell-layout-polishrun.md02_define/output/spec.mdThe agent app's frame is still the scaffold cut: a header showing a plain name and a Sign out
button where every other Sustentus app shows the Clerk UserButton, no way back to the platform,
no surfaced theme toggle although next-themes is fully wired, an h-screen layout that fights
mobile browser chrome and the on-screen keyboard, and a max-w-3xl column floating unframed on
large screens. It reads generic rather than Sustentus, and stub 2's conversation polish would be
styling messages inside a frame that undermines them. Jamie confirmed the irritations and ruled
the pass visual/UX only (2026-08-27). Initiative: Scale the bridge / Q2-2026 Objective 3 —
Validate Technical Infrastructure & Payout Flow, inherited from agentic-app-foundation, the
batch that built this app — the tie is as indirect for polish as it was for the scaffold
(flagged, not dressed up).
A visual/UX pass over the agent app's shell, per the brand-guidelines skill, changing no
behaviour, no routes, and no data access. agentic-tenant-context has shipped and landed chrome
in this header (the tenant name and the console caller's "Switch tenant" affordance) — the pass
polishes that chrome in place; it does not revert it.
UserButton (sign-out
moves into its menu). Keep the tenant name and "Switch tenant" affordance, aligned into the
polished header. Add a visible, labelled link back to the platform app and a compact theme
toggle surfacing the existing next-themes wiring (light / dark / system — Clerk components
already follow the resolved theme via ThemeAwareClerkProvider).PLATFORM_APP_URL variable mirroring CONSOLE_APP_URL's semantics (a bare host is
accepted and completed to https, http for localhost), resolved by a small helper local to
apps/agent (lib/). Unset, it fails safe: the link simply doesn't render and nothing else
changes. The variable joins turbo.json → globalEnv in this same PR (strict env mode;
turbo/no-undeclared-env-vars enforces it).h-screen shell with dynamic-viewport sizing so the layout
tracks mobile browser chrome and the on-screen keyboard: the input stays visible and usable
with the keyboard open, sending never jumps the scroll position, and the page itself never
scrolls — only the conversation column does. This may adjust container and scroll classes in
components/assistant-chat.tsx — containers only; the messages, banners and empty state
inside them are stub 2's.brand-guidelines become the baseline stub 2
builds on, applied through the existing @sustentus/ui tokens already imported by
app/globals.css — no new palette definitions.Files: apps/agent/app/page.tsx, apps/agent/app/layout.tsx, apps/agent/app/globals.css,
apps/agent/components/providers.tsx, apps/agent/components/assistant-chat.tsx (containers
only), new small components under apps/agent/components/ and a helper under
apps/agent/lib/, turbo.json. The auth gate stays "authenticated user and nothing finer";
/sign-in remains the only public route; proxy.ts is untouched.
UserButton (sign-out lives in its menu; the old
name + Sign out pair is gone), a labelled platform link, and a theme toggle — and the
tenant name and console caller's "Switch tenant" affordance survive the polish.next-themes wiring.PLATFORM_APP_URL, catalogued in turbo.json →
globalEnv in this PR; with the variable unset the link is absent and everything else
behaves unchanged.proxy.ts and the route surface are untouched; no new route is public; no new data
access is added.agent-chat-experience-polish).apps/web — which belongs to
the later redirection batch./api/chat or /api/ai/availability behaviour, the availability probe's
semantics, or the error-sanitisation policy (raw provider errors stay out of the DOM).ThemeToggle to packages/ui — apps/web and apps/marketing keep
their own theme controls; the agent toggle stays local until a third consumer earns the
extraction.proxy.ts, the "authenticated user" gate, and Clerk configuration.agentic-tenant-context's header chrome are resolved above.03_build/output/notes.mdturbo.json: PLATFORM_APP_URL joins globalEnv (strict env mode), documented beside
CONSOLE_APP_URL whose parsing rules it shares.apps/agent/lib/platform-origin.ts (new): platformOrigin() — delegates to the shared
origin parser (resolveConsoleOrigin, @sustentus/services/shared) rather than duplicating
it, per the "grep before writing a helper" rule; unset/unusable input → null → the header
omits the link. No new test file: the parsing logic already lives (and is exercised) in
packages/services; the local helper is a one-line delegation.apps/agent/components/theme-toggle.tsx (new): compact dropdown (light / dark / system)
over the existing next-themes wiring. Trigger icon swaps via CSS dark: classes (the shared
theme's class-strategy @custom-variant dark), so hydration needs no theme knowledge.apps/agent/app/page.tsx: header now shows the standard Clerk UserButton (name +
Sign out pair removed — also drops the currentUser() call), the platform link (rendered
only when PLATFORM_APP_URL resolves; text hidden below sm, aria-label carries the
name), the theme toggle, and keeps agentic-tenant-context's chrome (tenant name,
"Switch tenant"). Shell is now h-dvh + overflow-hidden (page never scrolls); the
conversation column is a bg-background card — full-bleed on mobile, rounded-xl border shadow-sm on sm+ floating on the bg-muted/30 canvas, header content aligned to the
same max-w-3xl column. Picker and no-workspace states sit in their own internal scroll
wrappers.apps/agent/app/layout.tsx: viewport.interactiveWidget = "resizes-content" — the
on-screen keyboard resizes the layout so the h-dvh shell shrinks and the input stays
visible while typing.apps/agent/app/globals.css: overscroll-behavior-y: none on html/body — no
pull-to-refresh wiping the ephemeral conversation, no page-level bounce.apps/agent/components/assistant-chat.tsx: containers only — scroll container gains
overscroll-contain + its own horizontal padding (moved off main), input strip loses its
now-redundant bg-background and gains matching padding. Messages, banners, empty state
untouched (stub 2).UserButton (sign-out in its menu), labelled platform link, theme toggle;
tenant name + "Switch tenant" survive — all in page.tsx.bg-background, bg-muted/30, borders);
Clerk components follow via the existing ThemeAwareClerkProvider; toggle drives
light / dark / system through next-themes.h-dvh, interactiveWidget: resizes-content, overscroll-contain,
fixed shell — only the column scrolls) but the assertion is device-empirical; left for
the Verify smoke on the preview rather than ticked from code reading.max-w-3xl column.PLATFORM_APP_URL in globalEnv this PR; unset → link absent, nothing else changes.proxy.ts untouched, no route added or made public, no new data access (the one new
services import is the pure /shared origin parser).UserButton popover
and the theme dropdown itself.PLATFORM_APP_URL is a new Vercel env var for the agentic-interface project — the
preview will render no platform link until it is set there. Unset is a spec-conformant
state (link absent), so the link's presence can only be smoked once the variable is
configured; everything else is verifiable without it.04_verify/output/verify.mdagentic-interface preview sits behind Vercel deployment
protection (SSO) — every route, /sign-in included, 302s to vercel.com before the app is
reached — so no preview surface is agent-reachable; deploy status verified instead
(branch preview READY for the code head), all visual smoke lines are the operator's.
Earlier .icm-only commits correctly skipped the app build (turbo-ignore); the final code
head rebuilt it.PLATFORM_APP_URL read + globalEnv entry land in the same PR, same name; fail-safe
parser is protocol-allowlisted). Release-process items: the variable is not yet set on
the agentic-interface Vercel project (owner action before announcing — unset is
spec-conformant, the link is simply absent); docs-sync (deployment env catalogue) and
changelog belong to Ship; rollback is a plain squash-revert (no migration)./code-review skill ran — single-pass inline, no subagent verify fan-out
(Agent tool unavailable in the fork). Correctness clean; 2 cleanup findings, both fixed on
branch in 80b5f60: theme menu now uses DropdownMenuRadioGroup/RadioItem (real
aria-checked semantics, manual check-icon logic deleted); header's dead
bg-background/80 backdrop-blur-sm reduced to bg-background (nothing can pass under a
non-overlay header in an overflow-hidden shell).proxy.ts untouched, no route added or made public, SignOutButton → UserButton is a
like-for-like Clerk surface swap, and the env var feeds a server-side origin parser that
only ever emits http(s) origins or null.UserButton + labelled platform link + theme toggle; tenant name and
"Switch tenant" survive — code paths traced in the diff: all six elements render from
apps/agent/app/page.tsx's header block, tenant chrome preserved conditionally as
before (agent). Visual pass on the preview: operator.dark: icon swap rides the shared class-strategy
variant, Clerk follows via the existing ThemeAwareClerkProvider (agent, from code +
theme config). Visual pass in both themes incl. the UserButton popover: operator.h-dvh + interactiveWidget: resizes-content +
overscroll-contain, page overflow-hidden) but device-empirical: operator, on
iOS Safari first (it ignores interactive-widget; Android proves little for it).max-w-3xl column) — operator visual pass.PLATFORM_APP_URL catalogued in globalEnv this PR; unset → link absent, nothing
else changes — lockstep and fail-safe verified in code (agent). With the var unset in
Vercel today, "link absent" is the expected preview state; the rendered-link half is
demonstrable only after the owner sets the variable: operator, post-config.proxy.ts and the route surface untouched; no new public route; no new data access —
diff-verified: no proxy/route/API changes, the one new services import is the pure
/shared origin parser (agent).UserButton menu (operator — the preview's Vercel SSO wall makes every signed-in
check theirs).aria-checked semantics — fixed on branch (80b5f60).PLATFORM_APP_URL absent from the agentic-interface Vercel project — needs owner
action before the platform link exists anywhere deployed (set for Production + Preview;
runtime read, so globalEnv alone does not deliver it).#build — operator records it during
the preview pass (visibility, not a gate).CLAUDE.md's monorepo map omits apps/agent and apps/console
(pre-existing) — a /pipeline tweak candidate so orientation stops lagging two apps.05_ship/output/changelog.mdLive page: apps/help/app/changelog/2026-08-28-agent-shell-layout-polish/page.mdx
(https://help.sustentus.com/changelog/2026-08-28-agent-shell-layout-polish)
title: The assistant now fits your phone and follows your theme date: 2026-08-28T09:00:00Z personas: [customer, expert, csm, sdm, admin, vendor] slug: agent-shell-layout-polish pr: https://github.com/sustentus/sustentus/pull/905
The first two releases were about what the assistant can do. This one is about the room it does it in — the frame around the conversation now behaves like the rest of Sustentus.
On a phone, the conversation cooperates. The message box stays visible while you type instead of hiding behind the keyboard, sending a message no longer jumps the page around, and only the conversation scrolls — the page itself stays put. A stray downward swipe no longer reloads the page either, which matters here: conversations are not saved, so an accidental refresh used to throw yours away.
Light, dark, or follow your device. A new control in the header sets the theme, and everything follows it — the conversation, the account menu, all of it.
The header now works like every other Sustentus app. Your account lives behind your picture at the top right, with sign out in its menu, and a link takes you back to the main Sustentus platform when you are done here. For staff and partners, the workspace name and the way you switch between workspaces stay where they were.
On a big screen the conversation holds its shape — a framed column rather than text floating in space.
05_ship/output/investor-update.mdWho it's for: Everyone — all six personas What shipped: The assistant's frame caught up: the standard account menu, a link back to the platform, light/dark/system theme, and a layout that works properly on phones. Why it matters: A credible surface for the assistant as it grows — Scale the Bridge, supporting Q2-2026 Objective 3 (Validate Technical Infrastructure & Payout Flow).
Dig deeper: https://github.com/sustentus/sustentus/pull/905 · https://help.sustentus.com/changelog/2026-08-28-agent-shell-layout-polish
05_ship/output/release.mdtechnical/deployment (PLATFORM_APP_URL joins the agent env catalogue, with
the runtime-read caveat and the fail-safe) and technical/applications (the agent header's
platform link) — updated in this PR.github/workflows/ship-note.yaml reads it from the
squash and emails the #product-update channel; nothing sends before the authorised mergeapps/docs/archive/pipeline-runs/; the agentic-ui-polish
batch stays live in intake — stub 2 (agent-chat-experience-polish) is not yet spun outUserButton + labelled platform link + theme toggle; tenant name and "Switch
tenant" survive — diff-traced (agent) + operator preview pass (verify.md)h-dvh · interactiveWidget: resizes-content · overscroll-contain);
the device smoke is the operator's line in verify.md, and the merge is authorised past
it by Jamie's gatePLATFORM_APP_URL catalogued in globalEnv this PR; unset → link absent, nothing else
changes — code-verified (agent); setting it on the agentic-interface Vercel project is
the recorded owner actionproxy.ts and the route surface untouched; no new public route; no new data access —
diff-verified (agent)