Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions design-system/apps/design-lab/src/preview/FlowChatToolGallery.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@
margin-top: 42px;
}

.flow-chat-tool-sequence {
display: grid;
min-inline-size: 0;
gap: var(--openbitfun-space-5);
}

.flow-chat-tool-sequence__rows {
min-inline-size: 0;
}

.flow-chat-tool-sequence__rows > * + * {
margin-block-start: var(--openbitfun-control-flow-chat-flow-item-gap);
}

.flow-chat-tool-sequence__rows > [data-openbitfun-component='flow-chat-tool-card'][data-openbitfun-attention='ambient'][data-openbitfun-expanded-shell='false']
+ [data-openbitfun-component='flow-chat-tool-card'][data-openbitfun-attention='ambient'][data-openbitfun-expanded-shell='false'] {
margin-block-start: 0;
}

.flow-chat-tool-gallery > .component-catalog-section-heading {
margin-block: 0 -8px;
padding-top: 34px;
Expand Down
47 changes: 47 additions & 0 deletions design-system/apps/design-lab/src/preview/FlowChatToolGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import {
ArrowRight,
Eye,
} from "lucide-react";
import { useState } from "react";
import { TabGroup } from "@openbitfun/ui";
import { AgentWaitToolCard, SkillToolCard, TerminalControlToolCard, TodoToolCard } from "@openbitfun/ui/flow-chat";
import { useI18n } from "../i18n";
import { getComponentDescription } from "../i18n/componentMetadata";
import {
Expand All @@ -28,6 +31,47 @@ const productOwnedToolExamples = [
"PatchCanvas",
] as const;

function ToolSequencePreview() {
const { t } = useI18n();
const [status, setStatus] = useState<"running" | "completed" | "error">("running");
const [expanded, setExpanded] = useState(false);
const completed = status === "completed";
return <section className="flow-chat-tool-sequence" data-openbitfun-pattern="tool-sequence">
<TabGroup
size="sm"
value={status}
onValueChange={(value) => setStatus(value as typeof status)}
items={[
{ value: "running", label: t("components.preview.flowChat.running") },
{ value: "completed", label: t("components.preview.flowChat.completed") },
{ value: "error", label: t("components.preview.flowChat.failed") },
]}
/>
<div className="flow-chat-tool-sequence__rows">
<SkillToolCard action="Skill" status="completed" summary="Read the public component contract" />
<TodoToolCard
title="Tasks"
status={status}
summary="Align long workspace paths and tool summaries with the conversation text column"
mode="standard"
loading={status === "running"}
allCompleted={completed}
completedCount={completed ? 3 : 1}
totalCount={3}
items={[
{ key: "columns", content: "Align public and product row columns", status: "completed" },
{ key: "detail", content: "Verify expansion with long text and file paths", status: completed ? "completed" : "in_progress" },
{ key: "replay", content: "Review the completed transcript", status: completed ? "completed" : "pending" },
]}
isExpanded={expanded}
onToggle={() => setExpanded(!expanded)}
/>
<AgentWaitToolCard action="AgentWait" status={status} summary={t(`components.preview.flowChat.${status === "error" ? "failed" : status}`)} />
<TerminalControlToolCard action="TerminalControl" status="completed" summary="src/features/workspace/remote/connection-profile-with-a-long-name.ts" />
</div>
</section>;
}

function ToolSpecimenCard({
componentName,
specimen,
Expand Down Expand Up @@ -67,6 +111,8 @@ export function FlowChatToolGallery({
<p>{t("components.flowChat.toolsDescription")}</p>
</header>

<ToolSequencePreview />

{toolCardEntries.map(({ component, definition }) => (
<section className="flow-chat-tool-group" key={component.name}>
<header className="flow-chat-tool-group__heading">
Expand Down Expand Up @@ -105,6 +151,7 @@ export function FlowChatToolGallery({
<div>
<strong>{t("components.flowChat.dedicatedTitle")}</strong>
<p>{t("components.flowChat.dedicatedDescription")}</p>
<code>ModelThinkingDisplay · ExploreGroupRenderer · RuntimeStatusSlot</code>
</div>
<code>{productOwnedToolExamples.join(" · ")}</code>
</aside>
Expand Down
14 changes: 8 additions & 6 deletions design-system/packages/design-tokens/src/system.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -495,19 +495,21 @@
"flowChat": {
"$description": "Shared FlowChat rhythm for product and package-owned conversation surfaces.",
"$type": "dimension",
"flowItemGap": { "$value": "0.42rem" },
"rowIconSize": { "$value": "{control.icon.sizeSm}" },
"rowIconGap": { "$value": "{space.1}" },
"flowItemGap": { "$value": "{space.2}" },
"turnGap": { "$value": "{space.4}" },
"inlineGap": { "$value": "0.35rem" },
"controlGap": { "$value": "0.5rem" },
"controlPaddingBlock": { "$value": "0.35rem" },
"inlineGap": { "$value": "{space.1}" },
"controlGap": { "$value": "{space.2}" },
"controlPaddingBlock": { "$value": "{space.1}" },
"controlPaddingInline": { "$value": "0.75rem" },
"contentPaddingInline": { "$value": "3rem" },
"contentPaddingInlineMobile": { "$value": "1.5rem" },
"cardGap": { "$value": "0.42rem" },
"cardGap": { "$value": "{control.flowChat.flowItemGap}" },
"cardRadius": { "$value": "{radius.base}" },
"cardPaddingBlock": { "$value": "0.625rem" },
"cardPaddingInline": { "$value": "0.75rem" },
"cardExpandedPaddingBlock": { "$value": "0.5rem" },
"cardExpandedPaddingBlock": { "$value": "{space.2}" },
"cardExpandedPaddingInline": { "$value": "0.625rem" },
"codeBlockPaddingBlock": { "$value": "0.55rem" },
"codeBlockPaddingInline": { "$value": "0.75rem" }
Expand Down
14 changes: 14 additions & 0 deletions design-system/packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,17 @@ long description while metadata and sibling actions retain their own slots.
Lab Patterns includes long paths, large change counts, expandable detail and
disabled actions. The current Web UI has no direct ActivityItem consumer;
FlowChat ambient tool cards keep their separate presentation contract.

Ambient FlowChat summaries, product thinking/explore headers and runtime status
share a 14px icon column followed by a 4px gap. The icon column starts at the
transcript body edge; heading text starts 18px after that edge. Loading, tool
and disclosure glyphs occupy that same column. Expanded thinking prose remains
aligned with the body edge; bordered tool detail retains its own content inset.
The 12px/4px Figma inline trace is a smaller typography scene; the existing
product 14px glyph size is retained when applying its gap to these summaries.

FlowChat vertical composition uses an 8px item/section gap and 4px inline gap.
Collapsed ambient tool runs use their 22px minimum line boxes without extra
spacing between adjacent rows; expanded/prominent cards retain the section gap.
The enclosing composition owns those gaps, and card bodies own their internal
padding. The Lab tool sequence demonstrates both arrangements with real cards.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function ActivityToolCardBase({
) : undefined}
icon={(
<ToolCardStatusSlot
size={14}
defaultIcon={defaultIcon}
status={status}
toolIcon={icon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const ambientToolCardMeta = {
"color.status.danger.content",
"control.height.md",
"control.toolCard.ambientRowMinBlockSize",
"control.flowChat.rowIconSize",
"control.flowChat.rowIconGap",
"font.family.sans",
"font.size.sm",
"font.size.xl",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,8 +454,8 @@
display: flex;
min-block-size: max(1lh, var(--openbitfun-control-tool-card-ambient-row-min-block-size));
align-items: center;
gap: var(--openbitfun-space-1);
padding-inline: var(--openbitfun-space-1) var(--openbitfun-space-2);
gap: var(--openbitfun-control-flow-chat-row-icon-gap);
padding-inline: 0 var(--openbitfun-space-2);
border: 0;
background: transparent;
box-shadow: none;
Expand All @@ -476,9 +476,8 @@
}

.ambientSurface .iconSlot {
--_tool-card-action-size: var(--openbitfun-space-5);

margin-inline-end: 1px;
--_tool-card-action-size: var(--openbitfun-control-flow-chat-row-icon-size);
--_flow-chat-tool-card-icon-size: var(--openbitfun-control-flow-chat-row-icon-size);
}

.ambientAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,20 +211,23 @@
display: flex;
min-inline-size: 0;
align-items: center;
gap: var(--openbitfun-space-2);
gap: var(--openbitfun-control-flow-chat-row-icon-gap);
color: var(--openbitfun-color-content-muted);
font-size: var(--openbitfun-type-body-sm-font-size);
}

.todoCompactIcon {
display: inline-flex;
flex: 0 0 auto;
flex: 0 0 var(--openbitfun-control-flow-chat-row-icon-size);
inline-size: var(--openbitfun-control-flow-chat-row-icon-size);
block-size: var(--openbitfun-control-flow-chat-row-icon-size);
align-items: center;
justify-content: center;
}

.todoCompactIcon > svg {
inline-size: 0.875rem;
block-size: 0.875rem;
inline-size: 100%;
block-size: 100%;
}

.todoCompactMeta {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function RunCodeToolCard({
action={action}
content={summary}
extra={actions ? <ToolCardActions>{actions}</ToolCardActions> : undefined}
icon={<ToolCardStatusSlot status={status} toolIcon={<Code2 aria-hidden="true" />} />}
icon={<ToolCardStatusSlot size={14} status={status} toolIcon={<Code2 aria-hidden="true" />} />}
/>
)}
isExpanded={Boolean(isExpanded && hasDetails)}
Expand Down Expand Up @@ -181,7 +181,7 @@ export function WebFetchToolCard({
action={action}
content={<OverflowText className={styles.fetchTitle} title={typeof title === "string" ? title : undefined}>{title}</OverflowText>}
icon={(
<ToolCardStatusSlot
<ToolCardStatusSlot size={14}
defaultIcon={status === "completed" || status === "error" ? "tool" : "status"}
status={status}
toolIcon={<Globe aria-hidden="true" />}
Expand Down Expand Up @@ -260,7 +260,7 @@ export function DefaultToolCard({
action={displayName}
content={summary}
icon={hasVisibleToolCardStatusGlyph(status) || icon
? <ToolCardStatusSlot status={status} toolIcon={icon} />
? <ToolCardStatusSlot size={14} status={status} toolIcon={icon} />
: undefined}
/>
)}
Expand Down Expand Up @@ -337,7 +337,7 @@ export function ViewImageToolCard({
header={(
<AmbientToolCardHeader
action={statusText}
icon={<ToolCardStatusSlot status={status} toolIcon={<ImageIcon aria-hidden="true" />} />}
icon={<ToolCardStatusSlot size={14} status={status} toolIcon={<ImageIcon aria-hidden="true" />} />}
/>
)}
isExpanded={Boolean(source && isExpanded)}
Expand Down Expand Up @@ -461,7 +461,7 @@ export function TodoToolCard({
action={isExpanded ? undefined : title}
content={headerSummary}
icon={(
<ToolCardStatusSlot
<ToolCardStatusSlot size={14}
defaultIcon={status === "error" || status === "cancelled" || allCompleted ? "status" : "tool"}
status={allCompleted ? "completed" : status}
toolIcon={<ListTodo aria-hidden="true" />}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { ComponentMeta } from "../../registry.types";

const ambientTokens = [
"control.flowChat.rowIconSize",
"control.flowChat.rowIconGap",
"color.content.primary",
"color.content.secondary",
"color.content.muted",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
.statusLayer,
.iconLayer {
display: inline-flex;
inline-size: 100%;
block-size: 100%;
align-items: center;
justify-content: center;
transition: opacity var(--openbitfun-motion-duration-fast) var(--openbitfun-motion-easing-standard);
Expand Down
14 changes: 8 additions & 6 deletions src/apps/data-migrator/ui/generated/design-system.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,23 @@
--openbitfun-control-composer-surface-padding: var(--openbitfun-space-2);
--openbitfun-control-composer-surface-radius: var(--openbitfun-radius-xl);
--openbitfun-control-composer-toolbar-gap: var(--openbitfun-space-2);
--openbitfun-control-flow-chat-card-expanded-padding-block: 0.5rem;
--openbitfun-control-flow-chat-card-expanded-padding-block: var(--openbitfun-space-2);
--openbitfun-control-flow-chat-card-expanded-padding-inline: 0.625rem;
--openbitfun-control-flow-chat-card-gap: 0.42rem;
--openbitfun-control-flow-chat-card-gap: var(--openbitfun-control-flow-chat-flow-item-gap);
--openbitfun-control-flow-chat-card-padding-block: 0.625rem;
--openbitfun-control-flow-chat-card-padding-inline: 0.75rem;
--openbitfun-control-flow-chat-card-radius: var(--openbitfun-radius-base);
--openbitfun-control-flow-chat-code-block-padding-block: 0.55rem;
--openbitfun-control-flow-chat-code-block-padding-inline: 0.75rem;
--openbitfun-control-flow-chat-content-padding-inline: 3rem;
--openbitfun-control-flow-chat-content-padding-inline-mobile: 1.5rem;
--openbitfun-control-flow-chat-control-gap: 0.5rem;
--openbitfun-control-flow-chat-control-padding-block: 0.35rem;
--openbitfun-control-flow-chat-control-gap: var(--openbitfun-space-2);
--openbitfun-control-flow-chat-control-padding-block: var(--openbitfun-space-1);
--openbitfun-control-flow-chat-control-padding-inline: 0.75rem;
--openbitfun-control-flow-chat-flow-item-gap: 0.42rem;
--openbitfun-control-flow-chat-inline-gap: 0.35rem;
--openbitfun-control-flow-chat-flow-item-gap: var(--openbitfun-space-2);
--openbitfun-control-flow-chat-inline-gap: var(--openbitfun-space-1);
--openbitfun-control-flow-chat-row-icon-gap: var(--openbitfun-space-1);
--openbitfun-control-flow-chat-row-icon-size: var(--openbitfun-control-icon-size-sm);
--openbitfun-control-flow-chat-turn-gap: var(--openbitfun-space-4);
--openbitfun-control-height-lg: 48px;
--openbitfun-control-height-md: 40px;
Expand Down
28 changes: 28 additions & 0 deletions src/web-ui/src/flow_chat/_item-rhythm.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Composition owns inter-item space. Leaf views own only their content padding.
// A model round retains its trailing gap across virtual rows; enclosed content
// drops it so the enclosing section does not count the last gap twice.
@mixin apply($enclosed: false) {
> :is(.flow-text-block, .flow-thinking-item, .flowchat-flow-item,
.task-with-subagent-wrapper, .subagent-projection-text--truncated) {
margin: 0 0 var(--openbitfun-control-flow-chat-flow-item-gap) 0;
}

// Consecutive collapsed ambient tools form a continuous run of 22px lines.
> .flowchat-flow-item:has(
> .flow-tool-card-wrapper[data-openbitfun-attention='ambient']
[data-openbitfun-component='flow-chat-tool-card'][data-openbitfun-part='root'][data-openbitfun-expanded-shell='false']
):has(
+ .flowchat-flow-item
> .flow-tool-card-wrapper[data-openbitfun-attention='ambient']
[data-openbitfun-component='flow-chat-tool-card'][data-openbitfun-part='root'][data-openbitfun-expanded-shell='false']
) {
margin-bottom: 0;
}

@if $enclosed {
> :is(.flow-text-block, .flow-thinking-item, .flowchat-flow-item,
.task-with-subagent-wrapper, .subagent-projection-text--truncated):last-child {
margin-bottom: 0;
}
}
}
6 changes: 2 additions & 4 deletions src/web-ui/src/flow_chat/components/FlowTextBlock.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
@include markdownTypography.apply;

width: 100%;
margin: 0 0 var(--openbitfun-control-flow-chat-flow-item-gap) 0;
margin: 0;
font-size: var(--openbitfun-type-flow-body-font-size);
line-height: var(--openbitfun-type-flow-body-line-height);
/* Transparent border to match the box-sizing of bordered FlowChat cards,
* TerminalRegion, etc.) so that text and card content share the same left edge. */
border: 1px solid transparent;
border: 0;
box-sizing: border-box;

.markdown-renderer {
Expand Down
17 changes: 1 addition & 16 deletions src/web-ui/src/flow_chat/components/FlowToolCard.scss
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
.flowchat-flow-item {
margin: 0 0 var(--openbitfun-control-flow-chat-flow-item-gap) 0;
margin: 0;
font-size: var(--openbitfun-type-flow-body-font-size);
line-height: var(--openbitfun-type-flow-support-line-height);
}

/* A run of collapsed ambient tools reads like consecutive transcript lines.
* Keep the normal flow-item gap at either edge of the run, but do not insert a
* card gap between each one-line row. Expanded ambient cards retain section
* spacing because they are content surfaces rather than line items. */
.flowchat-flow-item:has(
> .flow-tool-card-wrapper[data-openbitfun-attention='ambient']
[data-openbitfun-component='flow-chat-tool-card'][data-openbitfun-part='root'][data-openbitfun-expanded-shell='false']
):has(
+ .flowchat-flow-item
> .flow-tool-card-wrapper[data-openbitfun-attention='ambient']
[data-openbitfun-component='flow-chat-tool-card'][data-openbitfun-part='root'][data-openbitfun-expanded-shell='false']
) {
margin-bottom: 0;
}

.flow-tool-card-wrapper {
display: flex;
flex-direction: column;
Expand Down
Loading
Loading