diff --git a/ui-tui/src/banner.ts b/ui-tui/src/banner.ts
index 80da8f43d70..748e5a452bc 100644
--- a/ui-tui/src/banner.ts
+++ b/ui-tui/src/banner.ts
@@ -79,8 +79,8 @@ const colorize = (art: string[], gradient: readonly number[], c: ThemeColors): L
return art.map((text, i) => [p[gradient[i]!] ?? c.muted, text])
}
-export const LOGO_WIDTH = 98
-export const CADUCEUS_WIDTH = 30
+export const LOGO_WIDTH = Math.max(...LOGO_ART.map(line => line.length))
+export const CADUCEUS_WIDTH = Math.max(...CADUCEUS_ART.map(line => line.length))
export const logo = (c: ThemeColors, customLogo?: string): Line[] =>
customLogo ? parseRichMarkup(customLogo) : colorize(LOGO_ART, LOGO_GRADIENT, c)
diff --git a/ui-tui/src/components/appLayout.tsx b/ui-tui/src/components/appLayout.tsx
index 2e35c75c307..8b69b9e4425 100644
--- a/ui-tui/src/components/appLayout.tsx
+++ b/ui-tui/src/components/appLayout.tsx
@@ -112,9 +112,9 @@ const TranscriptPane = memo(function TranscriptPane({
{row.msg.kind === 'intro' ? (
-
+
- {row.msg.info && }
+ {row.msg.info && }
) : row.msg.kind === 'panel' && row.msg.panelData ? (
diff --git a/ui-tui/src/components/branding.tsx b/ui-tui/src/components/branding.tsx
index b7590f695e8..4f2bbb5eae5 100644
--- a/ui-tui/src/components/branding.tsx
+++ b/ui-tui/src/components/branding.tsx
@@ -29,31 +29,92 @@ function InlineLoader({ label, t }: { label: string; t: Theme }) {
export function ArtLines({ lines }: { lines: [string, string][] }) {
return (
- <>
+
{lines.map(([c, text], i) => (
-
+
{text}
))}
- >
+
)
}
-export function Banner({ t }: { t: Theme }) {
- const cols = useStdout().stdout?.columns ?? 80
+// Responsive Banner: full art → compact rule → text → hidden.
+//
+// Terminals can't scale glyphs, so "responsive" means picking a layout that
+// fits the available columns. Thresholds are picked so each tier reads
+// comfortably without forcing wrap or truncation drift on box-drawing edges.
+const TAG_FULL = 'Nous Research · Messenger of the Digital Gods'
+const TAG_MID = 'Messenger of the Digital Gods'
+const TAG_TINY = 'Nous Research'
+const HIDE_BELOW = 34
+const COMPACT_FROM = 58
+
+const clip = (s: string, w: number) =>
+ w <= 0 ? '' : s.length > w ? `${s.slice(0, Math.max(0, w - 1))}…` : s
+
+const centerIn = (s: string, w: number) => {
+ const f = clip(s, w)
+ const slack = Math.max(0, w - f.length)
+ const left = slack >> 1
+
+ return `${' '.repeat(left)}${f}${' '.repeat(slack - left)}`
+}
+
+const ruleIn = (label: string, w: number) => {
+ const f = clip(label, Math.max(1, w - 4))
+ const slack = Math.max(0, w - f.length - 2)
+ const left = slack >> 1
+
+ return `${'─'.repeat(left)} ${f} ${'─'.repeat(slack - left)}`
+}
+
+function CompactBanner({ cols, t }: { cols: number; t: Theme }) {
+ // -4 keeps a margin so exact-edge rows don't trip terminal pending-wrap.
+ const w = Math.max(28, cols - 4)
+
+ return (
+
+ {ruleIn(t.brand.name, w)}
+ {centerIn(TAG_FULL, w)}
+ {'─'.repeat(w)}
+
+ )
+}
+
+export function Banner({ maxWidth, t }: { maxWidth?: number; t: Theme }) {
+ const term = useStdout().stdout?.columns ?? 80
+ const cols = Math.max(1, Math.min(term, maxWidth ?? term))
+
+ if (cols < HIDE_BELOW) {
+ return null
+ }
+
const logoLines = logo(t.color, t.bannerLogo || undefined)
+ const logoW = t.bannerLogo ? artWidth(logoLines) : LOGO_WIDTH
+
+ if (cols >= logoW + 2) {
+ return (
+
+
+
+ {t.brand.icon} {TAG_FULL}
+
+
+ )
+ }
+
+ if (cols >= COMPACT_FROM) {
+ return
+ }
+
+ const name = cols >= 52 ? t.brand.name : (t.brand.name.split(' ')[0] ?? t.brand.name)
+ const tag = cols >= 64 ? TAG_FULL : cols >= 46 ? TAG_MID : TAG_TINY
return (
- {cols >= (t.bannerLogo ? artWidth(logoLines) : LOGO_WIDTH) ? (
-
- ) : (
-
- {t.brand.icon} NOUS HERMES
-
- )}
-
- {t.brand.icon} Nous Research · Messenger of the Digital Gods
+ {t.brand.icon} {name}
+ {t.brand.icon} {tag}
)
}
@@ -96,8 +157,9 @@ function CollapseToggle({
const SKILLS_MAX = 8
const TOOLSETS_MAX = 8
-export function SessionPanel({ info, sid, t }: SessionPanelProps) {
- const cols = useStdout().stdout?.columns ?? 100
+export function SessionPanel({ info, maxWidth, sid, t }: SessionPanelProps) {
+ const term = useStdout().stdout?.columns ?? 100
+ const cols = Math.max(20, Math.min(term, maxWidth ?? term))
const heroLines = caduceus(t.color, t.bannerHero || undefined)
const leftW = Math.min((artWidth(heroLines) || CADUCEUS_WIDTH) + 4, Math.floor(cols * 0.4))
const wide = cols >= 90 && leftW + 40 < cols
@@ -241,13 +303,33 @@ export function SessionPanel({ info, sid, t }: SessionPanelProps) {
)}
-
-
- {t.brand.name}
- {info.version ? ` v${info.version}` : ''}
- {info.release_date ? ` (${info.release_date})` : ''}
-
-
+ {wide ? (
+
+
+ {t.brand.name}
+ {info.version ? ` v${info.version}` : ''}
+ {info.release_date ? ` (${info.release_date})` : ''}
+
+
+ ) : (
+ // Narrow layout hides the hero column; surface model/cwd/session
+ // here so they aren't lost.
+
+
+ {info.model.split('/').pop()}
+ · Nous Research
+
+
+ {info.cwd || process.cwd()}
+
+ {sid && (
+
+ Session:
+ {sid}
+
+ )}
+
+ )}
{/* ── Tools (expanded by default) ── */}
@@ -378,6 +460,7 @@ interface PanelProps {
interface SessionPanelProps {
info: SessionInfo
+ maxWidth?: number
sid?: string | null
t: Theme
}