Files
accounted/components/agent/ChatConversationView.tsx
T
Jakob Wennberg 9686b54b41 refactor(design): lock the border-radius ladder, one radius per role (#1607)
Seven radii were in circulation (4/5/6/8/12/16px + pill) with no rule for
which went where; one toolbar row on /transactions mixed four shape
languages. This locks a 4-tier ladder (design.md convention 16):

- pill: interactive toolbar controls (buttons, chips, pickers, segmented
  controls, toolbar search, count nubs)
- rounded-xl (12px): overlay tier: page panel, dialogs, slide-overs
- rounded-lg (8px): cards, form fields, popover/menu content, boxes
- rounded-sm (4px): nested leaves (menu items, checkboxes, kbd/code nubs)

Changes:
- New SegmentedControl primitive (pill-in-pill tablist, h-8) replaces the
  hand-rolled bg-muted/70 tablist copied across 11 files
- New ToolbarSearch primitive (pill, h-8) adopted on 9 page toolbars;
  dialog/picker searches keep the rounded-lg Input
- dialog.tsx 8px -> 12px, matching SettingsModal/slide-over/CommandPalette
- ContextPicker chips at the shared h-8 toolbar height
- ~300 rounded-md / bare rounded call sites remapped by role; auth icon
  tiles and the mobile nav sheet come down from 16px to 12px
- rounded-md, bare rounded, rounded-2xl and rounded-[Npx] are dead
  vocabulary, enforced by a new off-ladder-radius check in check:guards

Verified: lint 0 errors, 14422 unit tests pass, check:guards green, tsc
clean on all changed files, sandbox screenshots of transactions/
bookkeeping/granskning toolbars and the Ny verifikation dialog.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 08:55:37 +02:00

87 lines
3.1 KiB
TypeScript

'use client'
import { useMemo } from 'react'
import Link from 'next/link'
import { ArrowLeft } from 'lucide-react'
import AgentChat, { attachStagedOperations, normalizeStoredMessages } from './AgentChat'
import type { StoredStagedOperation } from '@/types'
import AgentAvatar from './AgentAvatar'
import ContextChip from './ContextChip'
import SandboxAgentPreview from './SandboxAgentPreview'
import { useAgentSheet } from './AgentSheetProvider'
import { useCompanyOptional } from '@/contexts/CompanyContext'
interface Props {
conversationId: string
intentId: string
contextRef: string | null
title: string
rawMessages: { role: string; content: unknown; hidden?: boolean }[]
// Proposals this conversation staged that are still awaiting an answer, so
// the approval card reappears on resume instead of the proposal silently
// waiting out its expiry in Granskning.
stagedOperations?: StoredStagedOperation[]
}
// Full-page conversation view. Wraps AgentChat with a header that shows the
// title (or intent label). AgentChat handles the streaming + input + render.
export default function ChatConversationView({
conversationId,
intentId,
contextRef,
title,
rawMessages,
stagedOperations,
}: Props) {
const initialMessages = useMemo(
() => attachStagedOperations(normalizeStoredMessages(rawMessages), stagedOperations ?? []),
[rawMessages, stagedOperations],
)
const { identity } = useAgentSheet()
const companyCtx = useCompanyOptional()
const isSandbox = companyCtx?.isSandbox ?? false
const agentName = identity.displayName?.trim() || null
return (
<>
<header className="flex items-center gap-3 border-b border-border px-5 py-4 shrink-0">
{/* Mobile-only back-to-list arrow. On desktop the sidebar is always
visible so a back button would be redundant. */}
<Link
href="/chat"
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors -ml-1"
aria-label="Tillbaka till konversationer"
>
<ArrowLeft className="h-4 w-4" />
</Link>
<AgentAvatar
avatarId={identity.avatarId}
size="sm"
alt={identity.displayName ?? 'Assistent'}
/>
<div className="min-w-0">
<h1 className="font-display text-lg leading-tight tracking-tight truncate">{title}</h1>
{/* Was printing context_ref raw, so the subtitle read
"invoice:5f3a-9c21-...": a database identifier, shown to an
accountant, under the title of their own conversation. */}
<ContextChip contextRef={contextRef} className="mt-0.5" />
</div>
</header>
<div className="flex-1 min-h-0 flex flex-col">
{isSandbox ? (
<SandboxAgentPreview agentName={agentName} />
) : (
<AgentChat
intentId={intentId}
contextRef={contextRef ?? undefined}
initialConversationId={conversationId}
initialMessages={initialMessages}
scrollerClassName="px-6 py-8"
/>
)}
</div>
</>
)
}