From 0987b09ab73456162c7d2b3bf19ea67511af2373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20=C5=BDec?= Date: Tue, 14 Jul 2026 12:00:28 +0200 Subject: [PATCH 1/3] feat(ui): add InfoCardRow component with responsive layout and Storybook examples --- .../src/components/ui/index.ts | 1 + .../src/components/ui/info-card-row.tsx | 70 +++++++++++++++++++ .../src/stories/InfoCardRow.stories.tsx | 68 ++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 openframe-frontend-core/src/components/ui/info-card-row.tsx create mode 100644 openframe-frontend-core/src/stories/InfoCardRow.stories.tsx diff --git a/openframe-frontend-core/src/components/ui/index.ts b/openframe-frontend-core/src/components/ui/index.ts index d41c5c3a5..3a4030141 100644 --- a/openframe-frontend-core/src/components/ui/index.ts +++ b/openframe-frontend-core/src/components/ui/index.ts @@ -83,6 +83,7 @@ export * from './chevron-button' export * from './circular-progress' export { CheckIcon, CheckCircleIcon as LucideCheckCircleIcon, XIcon as LucideXIcon, MinusIcon, XCircleIcon } from './custom-icons' export * from './dashboard-info-card' +export * from './info-card-row' export * from './device-card' export * from './device-card-compact' export * from './entity-image' diff --git a/openframe-frontend-core/src/components/ui/info-card-row.tsx b/openframe-frontend-core/src/components/ui/info-card-row.tsx new file mode 100644 index 000000000..1b74b2dc6 --- /dev/null +++ b/openframe-frontend-core/src/components/ui/info-card-row.tsx @@ -0,0 +1,70 @@ +import type * as React from 'react' +import { cn } from '../../utils/cn' + +export interface InfoCardRowSection { + title: string + caption: string + icon?: React.ReactNode +} + +export interface InfoCardRowProps { + lead: InfoCardRowSection + stats: [InfoCardRowSection, InfoCardRowSection] + className?: string +} + +const CELL = + 'flex items-center gap-[var(--spacing-system-s)] min-w-0 p-[var(--spacing-system-xsf)] md:p-[var(--spacing-system-m)] min-h-[60px] md:min-h-[76px]' + +function StatCell({ section, className }: { section: InfoCardRowSection; className?: string }) { + return ( +
+
+

{section.title}

+
+

+ {section.caption} +

+ {section.icon && ( +
+ {section.icon} +
+ )} +
+
+ {section.icon && ( +
+ {section.icon} +
+ )} +
+ ) +} + +export function InfoCardRow({ lead, stats, className }: InfoCardRowProps) { + return ( +
+
+ {lead.icon && ( +
+ {lead.icon} +
+ )} +
+

{lead.title}

+

{lead.caption}

+
+
+ +
+ + +
+
+ ) +} diff --git a/openframe-frontend-core/src/stories/InfoCardRow.stories.tsx b/openframe-frontend-core/src/stories/InfoCardRow.stories.tsx new file mode 100644 index 000000000..310add84d --- /dev/null +++ b/openframe-frontend-core/src/stories/InfoCardRow.stories.tsx @@ -0,0 +1,68 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite' +import { InfoCardRow } from '../components/ui/info-card-row' +import { CheckCircleIcon } from '../components/icons-v2-generated/signs-and-symbols/check-circle-icon' +import { TagIcon } from '../components/icons-v2-generated/shopping/tag-icon' + +// Stand-in company logo — the lead `icon` fills the 40px bordered avatar box. +// In real usage this is typically an `` logo (rendered with object-cover). +const companyLogo = ( +
+) + +const meta = { + title: 'UI/InfoCardRow', + component: InfoCardRow, + parameters: { + layout: 'padded', + docs: { + description: { + component: + 'A responsive summary row: a 50%-width lead section (icon + title/caption) and two 25%-width stat sections (title + caption + right icon). On mobile the two stat sections wrap into their own row beneath the lead (76px rows on desktop/tablet, 60px on mobile), and each stat icon drops onto the caption line. Neighbouring sections share inner borders; only the outer edges are rounded. Each section takes a required `title` and `caption`, and an optional `icon`.', + }, + }, + }, + tags: ['autodocs'], + argTypes: { + lead: { control: false, description: 'Lead section (50% width): { title, caption, icon? }' }, + stats: { + control: false, + description: 'Two stat sections (25% width each): { title, caption, icon? }', + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta + +export default meta +type Story = StoryObj + +/** Full row — lead logo + name/devices, and two stats with icons. Matches Figma. */ +export const Default: Story = { + args: { + lead: { title: 'Precision Accounting Partners', caption: '847 devices', icon: companyLogo }, + stats: [ + { + title: 'Resolved this week', + caption: '312', + icon: , + }, + { title: 'Open tickets', caption: '2', icon: }, + ], + }, +} + +/** Icons omitted — text-only sections. */ +export const WithoutIcons: Story = { + args: { + lead: { title: 'Precision Accounting Partners', caption: '847 devices' }, + stats: [ + { title: 'Resolved this week', caption: '312' }, + { title: 'Open tickets', caption: '2' }, + ], + }, +} From 425720ccfe97e66d016c046e8452529163abad4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20=C5=BDec?= Date: Tue, 14 Jul 2026 12:31:43 +0200 Subject: [PATCH 2/3] fix(ui): prevent InfoCardRow from shrinking in flex containers --- openframe-frontend-core/src/components/ui/info-card-row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openframe-frontend-core/src/components/ui/info-card-row.tsx b/openframe-frontend-core/src/components/ui/info-card-row.tsx index 1b74b2dc6..583543ddc 100644 --- a/openframe-frontend-core/src/components/ui/info-card-row.tsx +++ b/openframe-frontend-core/src/components/ui/info-card-row.tsx @@ -45,7 +45,7 @@ export function InfoCardRow({ lead, stats, className }: InfoCardRowProps) { return (
From dfcb1a7e576123297edca4b4f83450233261ca9f Mon Sep 17 00:00:00 2001 From: Michael Assraf Date: Wed, 15 Jul 2026 03:33:35 +0200 Subject: [PATCH 3/3] fix --- .../src/components/chat/hooks/use-nats-chat-adapter.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openframe-frontend-core/src/components/chat/hooks/use-nats-chat-adapter.ts b/openframe-frontend-core/src/components/chat/hooks/use-nats-chat-adapter.ts index 9fb397b38..1b9d0174f 100644 --- a/openframe-frontend-core/src/components/chat/hooks/use-nats-chat-adapter.ts +++ b/openframe-frontend-core/src/components/chat/hooks/use-nats-chat-adapter.ts @@ -374,8 +374,14 @@ function updateTrailingAssistant( * here because the unified contract surfaces errors as banners, not * inline messages. (Hosts that need inline error bubbles can extend * the contract later.) + * + * Exported for host reuse (e.g. scripted marketing demos that rehydrate a + * stored `HistoricalMessage[]` via `processHistoricalMessagesWithErrors` and + * feed a `previewMode` EmbeddableChat). This mapper carries the segment/content + * shape only; author identity (name/avatar/authorType/timestamp) that a host + * needs for the demo is re-attached by the host from the same processed rows. */ -function mapProcessedToUnified( +export function mapProcessedToUnified( processed: Array<{ id: string role: 'user' | 'assistant' | 'error'