Strong typing, @owner/ui, and why the library grows by evidence.
A response to John Harlow's 4/16 thread. Design rationale and component API documentation for the Owner Agent component layer.
Scope as a component
The Owner Agent's component library isn't decorative. It's a direct response to prompt families surfaced by Daniel's Eval Dash. Every tool call we design corresponds to a scoped, measured family — which means the system grows by evidence, not opinion.
When a new prompt family emerges in the dashboard with meaningful time-recovery potential, we design the component to handle it. When an existing family can be consolidated, we collapse components. The library is a living map of what the agent must do well — grounded in what customers actually ask.
Tool calls ARE components
The agent doesn't invent UI for actions that already have excellent components. It uses yours. A menu-price edit doesn't get a custom diff card — it uses the Hestia menu-item editor, wrapped in an approve/skip contract. A pause-orders decision doesn't get a custom toggle — it uses the Hestia pause-orders component, wrapped the same way.
✕ Custom agent diff card
✓ Hestia component + agent chrome
Components we'd add to @owner/ui
These components don't exist in @owner/ui yet. We propose contributing them — not as an agent-only parallel library, but as first-class Owner UI primitives that happen to be agent-shaped.
@owner/ui/agent/ToolCallWrapperinterface ToolCallWrapperProps {
state: 'proposed' | 'approving' | 'completed' | 'rejected'
approveLabel: string
onApprove: () => void
onReject?: () => void
onUndo?: () => void
children: React.ReactNode
}@owner/ui/agent/AgentPanelinterface AgentPanelProps {
taskLabel: string
taskBreadcrumb?: string
state: 'idle' | 'thinking' | 'working'
defaultDock?: 'left' | 'right' | 'free'
onClose?: () => void
children: React.ReactNode
}@owner/ui/agent/Composerinterface ComposerProps {
placeholder?: string
isThinking?: boolean
suggestions?: string[]
onSend: (text: string, attachments?: File[]) => void
onSuggestion?: (text: string) => void
}@owner/ui/agent/Markinterface MarkProps {
state: 'resting' | 'listening' | 'thinking' | 'working' | 'completed'
size?: number // default 24px; solid silhouette below 16px
variant?: 'green' | 'cream' | 'mono'
showPin?: boolean
}@owner/ui/agent/CanvasOverlayinterface CanvasOverlayProps {
hoverDelay?: number // ms before showing (default 200)
chipLabel?: string // e.g. "Edit with Owner"
onChipClick?: () => void
children: React.ReactNode
}@owner/ui/agent/MemoryPillinterface MemoryPillProps {
items: string[]
label?: string
collapsible?: boolean
}Strong typing contract
John Harlow's case for a typed UIMessage interface is correct. Strongly-typed agent messages plus a well-modelled component architecture plus @owner/ui isn't three separate projects — it's one system with three sides. The typing forces the architecture; the architecture forces the library use; the library use forces the quality.
type MessageKind = 'text' | 'tool-call' | 'tool-result' | 'system'
interface BaseMessage {
id: string
role: 'user' | 'assistant' | 'system'
timestamp: number
}
interface TextMessage extends BaseMessage {
kind: 'text'
content: string
}
interface ToolCallMessage extends BaseMessage {
kind: 'tool-call'
toolName: string
toolUseId: string
input: Record<string, unknown>
state: 'proposed' | 'approving' | 'completed' | 'rejected'
approveLabel?: string
}
interface ToolResultMessage extends BaseMessage {
kind: 'tool-result'
toolUseId: string
content: string
isError?: boolean
}
type UIMessage = TextMessage | ToolCallMessage | ToolResultMessageHow this plugs into @owner/ui
The mock components in this prototype mirror real Hestia component APIs. A production integration replaces components/hestia/* imports with @owner/ui imports — no markup or prop changes required. The work to migrate is rename-imports, not redesign-everything.
// Before (prototype)
import { MenuItemEditor } from '@/components/hestia/MenuItemEditor'
// After (production)
import { MenuItemEditor } from '@owner/ui'v0.1 · April 20, 2026 · Derek Orr · Credits: Daniel Ternyak, John Harlow, Jonathan Minori, Chaz Moore, Jack Hanford
← Scenarios