6b506a9ca8
* feat(ui): frame-layout shell, pill buttons, 24px page titles (UI migration PR 1) The visual shell from the concept, zero behavior change: - New --frame token pair (40 18% 96% light / 0 0% 5% dark); the dashboard wrapper is bg-frame and <main> becomes a rounded 12px panel with its own inner scroll (md-gated; mobile keeps document flow + bottom nav) - Sidebar goes borderless/transparent on the frame - Buttons are pills app-wide (radius 99px, default 7px/16px padding, 13px text, icon buttons become circles), set once in components/ui/button.tsx - PageHeader locked at exactly 24px/32px Hedvig serif - MainContainer resets panel scroll on route change (Next's window scroll-to-top never fires for an inner scroll container) - chat layout + extension workspaces switch viewport-height formulas to h-full so they fill the panel instead of overflowing it by 20px - .claude/rules/design.md rewritten with the 14 locked UI-migration conventions from dev_docs/ui_migration_plan.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): clamp hand-rolled page titles to the locked 24px/32px Transaktioner (TransactionStatusBar) and 13 other pages hand-roll their h1 instead of using PageHeader, so they kept text-3xl/4xl after the shell change. Clamp them all to font-display text-2xl leading-8. Onboarding heroes and headline numbers are intentionally untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): keep the frame strip above the panel on banner-less accounts <main>'s 10px top margin was the first in-flow margin inside the shell wrapper, so it collapsed through the wrapper and pushed the whole shell down, showing white body background above the panel instead of the warm frame strip (only visible on real accounts: the sandbox banner blocked the collapse). Flex containers never collapse child margins, so the shell wrappers become md:flex md:flex-col. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
55 lines
2.3 KiB
TypeScript
55 lines
2.3 KiB
TypeScript
import { redirect } from 'next/navigation'
|
|
import ChatSidebar from '@/components/agent/ChatSidebar'
|
|
import {
|
|
getDashboardAuthContext,
|
|
getDashboardCompanyId,
|
|
getResolvedDashboardAgentProfile,
|
|
} from '../request-context'
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
// Two-pane chat layout: sidebar with conversations on the left, active
|
|
// conversation (or empty state) in the main panel. Both /chat and /chat/[id]
|
|
// share this layout so the sidebar doesn't unmount on conversation switches.
|
|
export default async function ChatLayout({ children }: { children: React.ReactNode }) {
|
|
const [{ supabase, user }, companyId, agent] = await Promise.all([
|
|
getDashboardAuthContext(),
|
|
getDashboardCompanyId(),
|
|
getResolvedDashboardAgentProfile(),
|
|
])
|
|
if (!user) redirect('/login')
|
|
if (!companyId) redirect('/onboarding')
|
|
|
|
// Block the chat surface until the agent is built. Without this a user
|
|
// who deep-links to /chat (bookmark, ⌘K, "+ Ny" elsewhere) lands on an
|
|
// empty conversations list with no Anna to talk to. The home route at /
|
|
// renders NewUserChecklist for the same state, so we forward there
|
|
// instead of duplicating the welcome screen here.
|
|
if (!agent?.verified_at) redirect('/')
|
|
|
|
const { data: conversations } = await supabase
|
|
.from('agent_conversations')
|
|
.select(
|
|
'id, intent_id, context_ref, title, pinned, archived, last_message_at, last_message_preview, created_at',
|
|
)
|
|
.eq('company_id', companyId)
|
|
.eq('archived', false)
|
|
.order('pinned', { ascending: false })
|
|
.order('last_message_at', { ascending: false, nullsFirst: false })
|
|
.limit(100)
|
|
|
|
return (
|
|
// MainContainer hands /chat a full-bleed h-full wrapper, so we don't
|
|
// need negative margins to break out of any chrome padding.
|
|
//
|
|
// dvh handles mobile browser chrome shrinking on scroll. Mobile: subtract
|
|
// the bottom nav (h-16 = 64px) + safe-area-inset-bottom so the chat
|
|
// pane fills the visible viewport exactly. Desktop: fill the frame
|
|
// panel (<main> has an explicit height there, so h-full resolves).
|
|
<div className="flex h-[calc(100dvh-4rem-env(safe-area-inset-bottom,0px))] md:h-full">
|
|
<ChatSidebar initialConversations={conversations ?? []} />
|
|
<div className="flex-1 min-w-0 flex flex-col bg-background">{children}</div>
|
|
</div>
|
|
)
|
|
}
|