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
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ type FlowChatPreviewDefinitionMap = {
type PreviewProps = FlowChatPreviewRenderOptions;

function ChatComposerPreview({ interactive, state }: PreviewProps) {
const [value, setValue] = useState("");
const [value, setValue] = useState(state === "expanded" ? "Review the attached project notes and explain how the implementation should behave across narrow and wide windows." : "");
const expanded = state === "expanded";
const queued = state === "queued";

return (
<div className="flow-chat-composer-preview">
<ChatComposer
busy={state === "busy"}
contextBar={(
contextBar={state === "default" ? undefined : (
<div className="flow-chat-composer-preview__context">
<span><Monitor aria-hidden="true" />This computer</span>
<span>OpenBitFun</span>
Expand Down
13 changes: 7 additions & 6 deletions design-system/packages/design-tokens/src/system.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -485,12 +485,12 @@
"chatComposer": {
"$type": "dimension",
"actionIconSize": { "$value": "14px" },
"compactGap": { "$value": "9px" },
"compactHeight": { "$value": "45px" },
"compactPaddingBlock": { "$value": "9px" },
"compactPaddingInline": { "$value": "9px" },
"compactTrackHeight": { "$value": "25px" },
"controlHeight": { "$value": "25px" }
"compactGap": { "$value": "{control.composer.surfaceGap}" },
"compactHeight": { "$value": "42px" },
"compactPaddingBlock": { "$value": "{control.composer.surfacePadding}" },
"compactPaddingInline": { "$value": "{control.composer.surfacePadding}" },
"compactTrackHeight": { "$value": "24px" },
"controlHeight": { "$value": "24px" }
},
"flowChat": {
"$description": "Shared FlowChat rhythm for product and package-owned conversation surfaces.",
Expand Down Expand Up @@ -585,6 +585,7 @@
"iconSizeMd": { "$value": "14px" }
},
"composer": {
"contextBlur": { "$type": "dimension", "$value": "10px" },
"$type": "dimension",
"minBlockSize": { "$value": "120px" },
"contextOffset": { "$value": "32px" },
Expand Down
12 changes: 6 additions & 6 deletions design-system/packages/design-tokens/tests/contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ test("AskUser geometry preserves the answered question reference contract", () =
});

test("ChatComposer geometry preserves the scaled compact capsule contract", () => {
assert.equal(tokens["control.chatComposer.compactGap"], "9px");
assert.equal(tokens["control.chatComposer.compactHeight"], "45px");
assert.equal(tokens["control.chatComposer.compactPaddingBlock"], "9px");
assert.equal(tokens["control.chatComposer.compactPaddingInline"], "9px");
assert.equal(tokens["control.chatComposer.compactTrackHeight"], "25px");
assert.equal(tokens["control.chatComposer.controlHeight"], "25px");
assert.equal(tokens["control.chatComposer.compactGap"], "12px");
assert.equal(tokens["control.chatComposer.compactHeight"], "42px");
assert.equal(tokens["control.chatComposer.compactPaddingBlock"], "8px");
assert.equal(tokens["control.chatComposer.compactPaddingInline"], "8px");
assert.equal(tokens["control.chatComposer.compactTrackHeight"], "24px");
assert.equal(tokens["control.chatComposer.controlHeight"], "24px");
});

test("FlowChat rhythm keeps compact rows line-like and Turn boundaries distinct", async () => {
Expand Down
2 changes: 2 additions & 0 deletions design-system/packages/theme-openbitfun/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ menu labels to secondary text (60%). Explicit imported action colors still win.
Action cards own `color.actionCard.background`: the light entry surface is black at 3% opacity. `surface.subtle` is a transient navy tint and `field.groupBackground` belongs to form groups, so neither represents this persistent action surface. Other modes retain their neutral action surface. Product Appearance preserves explicit legacy neutral-surface overrides in root and chrome.

Compact indicators own `color.numberBadge.background` and `color.keyHint.content` so reference light values (8% fill and 60% text) can coexist with existing dark/contrast values and explicit legacy Appearance overrides. The light Switch off track and resting scrollbar thumb use 10% black; KeyHint and Launcher resting fills use 8%. Long status labels keep content colors; StatusPill emphasis is opt-in.

`color.composer.border` and `color.composer.contextBackground` are the shared editor-surface contract for Composer and ChatComposer. Reference light uses an 8% border and 3% context tint; persistent entry cards and form groups keep their separate owners. Legacy explicit field-border and subtle-surface overrides populate the new keys only when absent. Composer shadow uses a 12px CSS box-shadow blur (the Figma effect radius; its generated filter drop-shadow uses a 6px standard deviation).
4 changes: 4 additions & 0 deletions design-system/packages/theme-openbitfun/src/dark.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
}
},
"color": {
"composer": {
"border": { "$type": "color", "$value": "{color.field.border}" },
"contextBackground": { "$type": "color", "$value": "{color.surface.subtle}" }
},
"numberBadge": {
"background": { "$type": "color", "$value": "{color.action.neutral.surface}" }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
}
},
"color": {
"composer": {
"border": { "$type": "color", "$value": "{color.field.border}" },
"contextBackground": { "$type": "color", "$value": "{color.surface.subtle}" }
},
"keyHint": {
"content": { "$type": "color", "$value": "{color.content.muted}" }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
}
},
"color": {
"composer": {
"border": { "$type": "color", "$value": "{color.field.border}" },
"contextBackground": { "$type": "color", "$value": "{color.surface.subtle}" }
},
"keyHint": {
"content": { "$type": "color", "$value": "{color.content.muted}" }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
}
},
"color": {
"composer": {
"border": { "$type": "color", "$value": "{color.field.border}" },
"contextBackground": { "$type": "color", "$value": "rgba(0, 0, 0, 0.03)" }
},
"numberBadge": {
"background": { "$type": "color", "$value": "{color.action.neutral.surfaceHover}" }
},
Expand Down Expand Up @@ -261,7 +265,7 @@
"lg": { "$value": "0 8px 16px rgba(16, 26, 39, 0.09)" },
"xl": { "$value": "0 12px 24px rgba(16, 26, 39, 0.11)" },
"raised": { "$value": "{shadow.sm}" },
"composer": { "$value": "0 2px 6px rgba(0, 0, 0, 0.08)" },
"composer": { "$value": "0 2px 12px rgba(0, 0, 0, 0.08)" },
"menu": { "$value": "0 4px 20px rgba(0, 0, 0, 0.12)" },
"overlay": { "$value": "0 4px 20px rgba(0, 0, 0, 0.12)" },
"accentGlow": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ test("default modes preserve the built-in Appearance anchor values", () => {
assert.equal(themes.light["color.identity.globalSearch.openFiles"], "#9e54ff");
assert.equal(themes.light["color.status.warning.surface"], "rgba(255, 140, 0, 0.1)");
assert.equal(themes.light["shadow.base"], "0 4px 8px rgba(16, 26, 39, 0.07)");
assert.equal(themes.light["shadow.composer"], "0 2px 6px rgba(0, 0, 0, 0.08)");
assert.equal(themes.light["shadow.composer"], "0 2px 12px rgba(0, 0, 0, 0.08)");
assert.equal(themes.light["shadow.menu"], "0 4px 20px rgba(0, 0, 0, 0.12)");
assert.equal(themes.light["shadow.overlay"], "0 4px 20px rgba(0, 0, 0, 0.12)");
assert.equal(themes.light["opacity.disabled"], 0.55);
Expand Down
2 changes: 2 additions & 0 deletions design-system/packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,3 +423,5 @@ Extra-large (`xl`) dialogs have an 800px maximum width and continue shrinking wi
PageHeader `md` uses the settings title with a primary 15px description; `display` uses the welcome heading and medium 17px introduction with a 12px gap. ActionCard uses 12px padding, section-heading typography (15px semibold), and a primary 13px single-line action description. Its inset outline does not inflate the 62px medium minimum height; longer content keeps the independent sibling actions and OverflowText behavior.

KeyHint uses 10px text on a 10px line and 2px block padding. StatusPill uses a 14px line with 2px block padding; opt into `emphasis` for short mode labels such as Ask, while ordinary status descriptions retain readable content colors. LauncherButton owns a 72px minimum width, 40px height, 10px side padding, 4px gap, 12px icon and 11px monospace text. Product shells may retain a deliberate compact greeting that expands into this geometry.

Composer and ChatComposer share the 16px surface radius, 8px padding, 12px content gap, composer border, context tint, and composer shadow. Compact ChatComposer uses a 24px action track plus 8px padding on each side and two 1px borders (42px border-box height); expanded height follows editor content. The generic context shell has no duplicate outer border or surface inset. Queue and editor state remain owned by their existing slots and product adapters.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ export const composerMeta = {
states: ["default", "focus-within", "with-context", "invalid", "disabled"],
tokens: [
"color.surface.raised",
"color.surface.subtle",
"color.composer.contextBackground",
"control.composer.contextBlur",
"color.surface.panel",
"color.border.subtle",
"color.content.primary",
"color.content.muted",
"color.content.disabled",
"color.field.border",
"color.composer.border",
"color.field.borderFocus",
"color.focus.ring",
"color.status.danger.border",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@

.root[data-has-context="true"] {
padding-block-start: var(--openbitfun-control-composer-context-offset);
border: var(--openbitfun-border-width-default) solid var(--openbitfun-color-border-subtle);
border-radius: var(--openbitfun-control-composer-surface-radius);
background: var(--openbitfun-color-surface-subtle);
background: var(--openbitfun-color-composer-context-background);
backdrop-filter: blur(var(--openbitfun-control-composer-context-blur));
-webkit-backdrop-filter: blur(var(--openbitfun-control-composer-context-blur));
}

.context {
Expand All @@ -53,7 +54,7 @@
flex-direction: column;
gap: var(--openbitfun-control-composer-surface-gap);
padding: var(--openbitfun-control-composer-surface-padding);
border: var(--openbitfun-border-width-default) solid var(--openbitfun-color-field-border);
border: var(--openbitfun-border-width-default) solid var(--openbitfun-color-composer-border);
border-radius: var(--openbitfun-control-composer-surface-radius);
background: var(--openbitfun-color-surface-raised);
box-shadow: var(--openbitfun-shadow-composer);
Expand All @@ -62,12 +63,6 @@
box-shadow var(--openbitfun-motion-duration-fast) var(--openbitfun-motion-easing-standard);
}

.root[data-has-context="true"] > .surface {
inline-size: calc(100% - var(--openbitfun-border-width-default) - var(--openbitfun-border-width-default));
margin-inline: var(--openbitfun-border-width-default);
margin-block-end: var(--openbitfun-border-width-default);
}

.surface:focus-within {
border-color: var(--openbitfun-color-field-border-focus);
box-shadow:
Expand Down Expand Up @@ -175,7 +170,20 @@
}
}

@media (prefers-reduced-transparency: reduce) {
.root[data-has-context="true"] {
background: var(--openbitfun-color-surface-panel);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}

@media (forced-colors: active) {
.root[data-has-context="true"] {
background: Canvas;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
.root[data-has-context="true"],
.surface,
.root[data-invalid="true"] > .surface {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ export const chatComposerMeta = {
states: ["default", "expanded", "queued", "focus-visible", "busy", "disabled"],
tokens: [
"border.width.default",
"color.action.neutral.border",
"color.composer.border",
"color.composer.contextBackground",
"control.composer.contextBlur",
"control.composer.surfaceRadius",
"control.composer.surfacePadding",
"control.composer.surfaceGap",
"color.action.neutral.content",
"color.action.neutral.contentDisabled",
"color.action.neutral.surface",
Expand Down Expand Up @@ -51,7 +56,7 @@ export const chatComposerMeta = {
"radius.lg",
"radius.2xl",
"radius.pill",
"shadow.base",
"shadow.composer",
"space.1",
"space.2",
"space.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@
min-inline-size: 0;
inline-size: 100%;
flex-direction: column;
border-radius: var(--openbitfun-radius-2xl);
border-radius: var(--openbitfun-control-composer-surface-radius);
color: var(--openbitfun-color-content-primary);
background: transparent;
font-family:var(--openbitfun-type-body-md-font-family);
}

.root[data-has-context="true"] {
/* Keep the band opaque while moving it toward the foreground: lighter in
dark themes and darker in light themes. */
background: color-mix(
in srgb,
var(--openbitfun-color-surface-panel) 94%,
var(--openbitfun-color-content-primary) 6%
);
background: var(--openbitfun-color-composer-context-background);
backdrop-filter: blur(var(--openbitfun-control-composer-context-blur));
-webkit-backdrop-filter: blur(var(--openbitfun-control-composer-context-blur));
}

.context {
Expand Down Expand Up @@ -50,13 +46,13 @@
min-block-size: var(--openbitfun-control-height-md);
box-sizing: border-box;
align-items: center;
gap: var(--openbitfun-space-3);
padding: var(--openbitfun-space-2);
border: var(--openbitfun-border-width-default) solid var(--openbitfun-color-action-neutral-border);
border-radius: var(--openbitfun-radius-2xl);
gap: var(--openbitfun-control-composer-surface-gap);
padding: var(--openbitfun-control-composer-surface-padding);
border: var(--openbitfun-border-width-default) solid var(--openbitfun-color-composer-border);
border-radius: var(--openbitfun-control-composer-surface-radius);
color: var(--openbitfun-color-content-primary);
background: var(--openbitfun-color-surface-panel);
box-shadow: var(--openbitfun-shadow-base);
background: var(--openbitfun-color-surface-raised);
box-shadow: var(--openbitfun-shadow-composer);
transition:
border-color var(--openbitfun-motion-duration-fast) var(--openbitfun-motion-easing-standard),
box-shadow var(--openbitfun-motion-duration-fast) var(--openbitfun-motion-easing-standard);
Expand All @@ -73,7 +69,7 @@
align-items: center;
padding-block: var(--openbitfun-control-chat-composer-compact-padding-block);
padding-inline: var(--openbitfun-control-chat-composer-compact-padding-inline);
border-radius: var(--openbitfun-radius-pill);
border-radius: var(--openbitfun-control-composer-surface-radius);
}

.surface[data-openbitfun-layout="expanded"] {
Expand Down Expand Up @@ -173,7 +169,20 @@
}
}

@media (prefers-reduced-transparency: reduce) {
.root[data-has-context="true"] {
background: var(--openbitfun-color-surface-panel);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}

@media (forced-colors: active) {
.root[data-has-context="true"] {
background: Canvas;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
.surface,
.surface:focus-within {
border-color: CanvasText;
Expand Down
16 changes: 6 additions & 10 deletions design-system/packages/ui/tests/chat-composer.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -248,24 +248,20 @@ test("ChatComposer geometry is driven by public system and semantic tokens", asy
assert.match(styles, /--openbitfun-control-chat-composer-action-icon-size/);
assert.match(styles, /--openbitfun-control-chat-composer-control-height/);
assert.match(styles, /--openbitfun-space-8/);
assert.match(styles, /--openbitfun-radius-2xl/);
assert.match(styles, /--openbitfun-control-composer-surface-radius/);
assert.match(styles, /--openbitfun-color-surface-panel/);
assert.match(styles, /--openbitfun-color-surface-subtle/);
assert.match(styles, /--openbitfun-color-surface-raised/);
const contextBackgroundRule = styles.match(
/\.\w+\[data-has-context=(?:"true"|true)\][^{]*\{[^}]*\}/,
);
assert.ok(contextBackgroundRule);
assert.match(
contextBackgroundRule[0],
/background:\s*color-mix\(in srgb,\s*var\(--openbitfun-color-surface-panel\)\s*94%,\s*var\(--openbitfun-color-content-primary\)\s*6%\)/,
);
assert.doesNotMatch(contextBackgroundRule[0], /\btransparent\b/);
assert.match(styles, /--openbitfun-color-action-neutral-border/);
assert.match(styles, /--openbitfun-shadow-base/);
assert.match(contextBackgroundRule[0], /background:\s*var\(--openbitfun-color-composer-context-background\)/);
assert.match(styles, /--openbitfun-color-composer-border/);
assert.match(styles, /--openbitfun-shadow-composer/);
assert.match(
styles,
/border:\s*var\(--openbitfun-border-width-default\)\s+solid\s+var\(--openbitfun-color-action-neutral-border\)/,
/border:\s*var\(--openbitfun-border-width-default\)\s+solid\s+var\(--openbitfun-color-composer-border\)/,
);
assert.match(styles, /min-block-size:\s*var\(--openbitfun-control-height-md\)/);
assert.match(styles, /grid-template-areas:\s*"start content end"/);
Expand All @@ -276,7 +272,7 @@ test("ChatComposer geometry is driven by public system and semantic tokens", asy
assert.match(styles, /"content content"\s*"start end"/);
assert.match(
styles,
/\[data-openbitfun-layout=(?:"compact"|compact)\][^{]*\{[^}]*block-size:\s*var\(--openbitfun-control-chat-composer-compact-height\)[^}]*border-radius:\s*var\(--openbitfun-radius-pill\)/,
/\[data-openbitfun-layout=(?:"compact"|compact)\][^{]*\{[^}]*block-size:\s*var\(--openbitfun-control-chat-composer-compact-height\)[^}]*border-radius:\s*var\(--openbitfun-control-composer-surface-radius\)/,
);
assert.match(
styles,
Expand Down
Loading
Loading