Skip to content

Commit 9e51317

Browse files
authored
Merge pull request #3349 from GCWing/gcwing/model-selector-capsule-maintenance
feat: improve model selection, capsule actions, and macOS maintenance
2 parents 0f1f170 + 1377cc6 commit 9e51317

94 files changed

Lines changed: 2251 additions & 844 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎design-system/AGENTS.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,10 @@ pnpm run design-system:check
4242

4343
Run `pnpm run design-system:check` for cross-package or release-boundary changes. Token and theme changes must also run the repository-level `pnpm run theme:color-audit:all`.
4444

45+
For shared icon rendering or geometry changes, use the focused package checks:
46+
47+
```bash
48+
pnpm --dir design-system run build:ui && node --test design-system/packages/ui/tests/icon.test.mjs design-system/packages/ui/tests/icon-slots.test.mjs
49+
```
50+
4551
Do not use browser automation or mock screenshots as visual proof. Design Lab is the manual authoring surface; source, build, HTTP, and package checks do not establish final visual fidelity.

‎design-system/apps/design-lab/src/i18n/messages.ts‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ export const enUSMessages = {
163163
"component.GitToolCard.description": "A prominent Git command card with output, warning, failure, and execution metadata.",
164164
"component.GlobSearchToolCard.description": "An ambient file-pattern search card with reusable result-list anatomy.",
165165
"component.GrepSearchToolCard.description": "An ambient text-search card with query metadata and a scrollable result preview.",
166-
"component.IconButton.description": "A labeled icon-only action with annotation, quiet, outlined, filled, and primary variants. An annotation provides a small, muted entry to supplemental help.",
166+
"component.IconButton.description": "A labeled icon action with annotation, quiet, glass, outlined, filled, and primary variants. Glass actions float over intact content.",
167+
"component.Capsule.description": "A capsule with a circular glass action floating over intact text and equal edge spacing.",
167168
"component.Input.description": "A native text field with independent leading and trailing content slots.",
168169
"component.KeyHint.description": "A semantic keyboard hint with optional decorative icon content.",
169170
"component.LoadingState.description": "A centered loading composition with an activity indicator and optional support copy.",
@@ -186,7 +187,7 @@ export const enUSMessages = {
186187
"component.SkillToolCard.description": "An ambient trace for skill loading progress and outcomes.",
187188
"component.Spinner.description": "A compact activity indicator with matrix and bar presentations.",
188189
"component.Switch.description": "Toggles a single setting between on and off with native checkbox semantics.",
189-
"component.TabGroup.description": "A compact single-selection tab list with optional leading icons, end actions, and automatic keyboard activation.",
190+
"component.TabGroup.description": "A compact single-selection tab list with leading icons, glass end actions over intact labels, and automatic keyboard activation.",
190191
"component.RollingText.description": "Vertical text replacement with coordinated width, interruption handling, and reduced-motion support. Try repeated clicks in the replacement specimen.",
191192
"component.ShimmerText.description": "A soft fade moves slowly across activity text, keeping every glyph visible. Reduced motion, high contrast and print keep the text static and readable.",
192193
"components.preview.replaceText": "Replace title",
@@ -557,6 +558,7 @@ export const enUSMessages = {
557558
"detail.option.primary": "Primary",
558559
"detail.option.quiet": "Quiet",
559560
"detail.option.annotation": "Annotation",
561+
"detail.option.glass": "Glass",
560562
"detail.option.text": "Text",
561563
"detail.option.sm": "Small",
562564
"detail.option.start": "Start",
@@ -969,7 +971,8 @@ export const zhCNMessages = {
969971
"component.GitToolCard.description": "关注型 Git 命令卡片,统一呈现输出、警告、失败与执行元数据。",
970972
"component.GlobSearchToolCard.description": "复用结果列表结构的低关注文件模式搜索卡片。",
971973
"component.GrepSearchToolCard.description": "带查询元数据和可滚动结果预览的低关注文本搜索卡片。",
972-
"component.IconButton.description": "带无障碍标签的纯图标组件,支持标注、安静、描边、填充和主要呈现方式。标注以小尺寸、弱化颜色提供补充说明入口。",
974+
"component.IconButton.description": "带无障碍标签的纯图标组件,支持标注、安静、毛玻璃、描边、填充和主要呈现方式。毛玻璃操作悬浮在完整内容上方。",
975+
"component.Capsule.description": "胶囊内容保持完整,圆形毛玻璃操作悬浮其上,边缘间距一致。",
973976
"component.Input.description": "使用原生输入语义,并提供独立前置与后置内容槽的文本字段。",
974977
"component.KeyHint.description": "使用键盘提示语义,并支持可选装饰图标的基础组件。",
975978
"component.LoadingState.description": "由活动指示器与可选说明文字组成的居中加载状态。",
@@ -992,7 +995,7 @@ export const zhCNMessages = {
992995
"component.SkillToolCard.description": "低关注 Skill 轨迹,用于呈现加载进度和执行结果。",
993996
"component.Spinner.description": "提供点阵与条形样式的紧凑活动指示器。",
994997
"component.Switch.description": "通过原生复选框语义在开启与关闭之间切换单项设置。",
995-
"component.TabGroup.description": "紧凑的单选标签组,支持前置图标、尾部操作与方向键自动切换。",
998+
"component.TabGroup.description": "紧凑的单选标签组,支持前置图标、悬浮于完整标签上的毛玻璃尾部操作与方向键自动切换。",
996999
"component.RollingText.description": "垂直滚动替换文本,宽度同步过渡,支持连续切换与减少动态效果。可在替换示例中连续点击体验。",
9971000
"component.ShimmerText.description": "柔和的渐隐在运行文字间缓慢流动,始终保留完整字形;减少动态效果、高对比度和打印时保持静态可读。",
9981001
"components.preview.replaceText": "替换标题",
@@ -1363,6 +1366,7 @@ export const zhCNMessages = {
13631366
"detail.option.primary": "主要",
13641367
"detail.option.quiet": "安静",
13651368
"detail.option.annotation": "标注",
1369+
"detail.option.glass": "毛玻璃",
13661370
"detail.option.text": "文字",
13671371
"detail.option.sm": "小",
13681372
"detail.option.start": "起始",
@@ -1772,7 +1776,8 @@ export const zhTWMessages = {
17721776
"meta.category.primitive": "基礎元件",
17731777
"meta.category.other": "元件",
17741778
"component.ActionCard.description": "可獨立設定前置內容、標題、說明、選取狀態和同級尾端操作的可互動卡片。",
1775-
"component.IconButton.description": "帶無障礙標籤的純圖示元件,支援標註、安靜、描邊、填滿和主要呈現方式。標註以小尺寸、弱化顏色提供補充說明入口。",
1779+
"component.IconButton.description": "帶無障礙標籤的純圖示元件,支援標註、安靜、毛玻璃、描邊、填滿和主要呈現方式。毛玻璃操作懸浮在完整內容上方。",
1780+
"component.Capsule.description": "膠囊內容保持完整,圓形毛玻璃操作懸浮其上,邊緣間距一致。",
17761781
"component.MobileActionSheet.description": "用於統一呈現一般和危險操作清單的可關閉行動端彈層。",
17771782
"component.MobileBadge.description": "用於狀態、身分與中繼資料的緊湊行動端標籤。",
17781783
"component.MobileBanner.description": "支援語義色調和可選操作的行動端行內狀態橫幅。",
@@ -1839,7 +1844,7 @@ export const zhTWMessages = {
18391844
"component.SkillToolCard.description": "低關注 Skill 軌跡,用於呈現載入進度和執行結果。",
18401845
"component.Spinner.description": "提供點陣與條形樣式的緊湊活動指示器。",
18411846
"component.Switch.description": "以原生核取方塊語意在開啟與關閉之間切換單一設定。",
1842-
"component.TabGroup.description": "緊湊的單選標籤群組,支援前置圖示、尾端操作與方向鍵自動切換。",
1847+
"component.TabGroup.description": "緊湊的單選標籤群組,支援前置圖示、懸浮於完整標籤上的毛玻璃尾端操作與方向鍵自動切換。",
18431848
"component.RollingText.description": "垂直捲動替換文字,寬度同步過渡,支援連續切換與減少動態效果。可在替換範例中連續點擊體驗。",
18441849
"component.ShimmerText.description": "柔和的漸隱在執行文字間緩慢流動,始終保留完整字形;減少動態效果、高對比度和列印時保持靜態可讀。",
18451850
"components.preview.replaceText": "替換標題",
@@ -2113,6 +2118,7 @@ export const zhTWMessages = {
21132118
"detail.iconPosition": "圖示位置",
21142119
"detail.option.outline": "描邊",
21152120
"detail.option.annotation": "標註",
2121+
"detail.option.glass": "毛玻璃",
21162122
"detail.option.fill": "填滿",
21172123
"detail.option.sm": "小",
21182124
"detail.option.start": "起始",

‎design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CapsulePreview } from "../preview/CapsulePreview";
12
import { Fragment, useEffect, useMemo, useState, type CSSProperties } from "react";
23
import { ComponentOverview } from "../preview/ComponentOverview";
34
import { getComponentPresentation, getInitialPreviewState, hasComponentOverview } from "../preview/componentPresentation";
@@ -163,7 +164,7 @@ type PageHeaderAlign = "center" | "start";
163164
type PageHeaderSize = "display" | "lg" | "md" | "sm";
164165

165166
const buttonVariants = ["outline", "fill", "secondary", "primary", "text"] as const;
166-
const iconButtonVariants = ["annotation", "quiet", "outline", "fill", "primary"] as const;
167+
const iconButtonVariants = ["annotation", "quiet", "glass", "outline", "fill", "primary"] as const;
167168
const iconButtonSizes = ["xs", "standard", "sm", "md", "lg"] as const;
168169
const buttonInspectorStates = ["default", "hover", "active"] as const;
169170
const fieldOrientations = ["vertical", "horizontal"] as const;
@@ -231,6 +232,7 @@ const optionLabelKeys: Readonly<Record<string, MessageKey>> = {
231232
open: "detail.option.open",
232233
outline: "detail.option.outline",
233234
annotation: "detail.option.annotation",
235+
glass: "detail.option.glass",
234236
primary: "detail.option.primary",
235237
quiet: "detail.option.quiet",
236238
raised: "detail.option.raised",
@@ -537,6 +539,9 @@ export function ComponentDetailPage({
537539
return `import { Icon } from "@openbitfun/ui";\n\n<Icon name="${iconName}" size="${iconSize}" tone="${iconTone}" />`;
538540
}
539541

542+
if (component.name === "Capsule") {
543+
return `import { Button, Capsule, Icon, IconButton } from "@openbitfun/ui";\n\n<Capsule size="sm" action={\n <IconButton aria-label="${t("components.preview.close")}" icon={<Icon name="xmark" />} onClick={remove} />\n}>\n <Button size="sm" variant="outline">${t("components.preview.modalProviderName")}</Button>\n</Capsule>`;
544+
}
540545
if (component.name === "IconButton") {
541546
const stateProps = `${inspectorDisabled ? " disabled" : ""}${inspectorLoading ? " loading" : ""}`;
542547
const annotation = iconButtonVariant === "annotation";
@@ -904,6 +909,7 @@ export function ComponentDetailPage({
904909
);
905910
}
906911

912+
if (component.name === "Capsule") return <CapsulePreview key={state} state={state} />;
907913
if (component.name === "Icon") {
908914
return <Icon name={iconName} size={iconSize} tone={iconTone} />;
909915
}

‎design-system/apps/design-lab/src/pages/ComponentsPage.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useRef, useState } from "react";
22
import { ComponentDetailPage } from "./ComponentDetailPage";
33
import { BrandPreview } from "../preview/BrandPreview";
4+
import { CapsulePreview } from "../preview/CapsulePreview";
45
import { AppWindow, Heading, Keyboard, List, Rows3, MousePointerClick, PanelTop, ToggleLeft } from "lucide-react";
56
import { VoiceCallPreview, VoiceParticlePreview } from "../components/VoiceCallPreview";
67
import {
@@ -607,6 +608,8 @@ export function ComponentCardPreview({ component }: { component: ComponentMeta }
607608
shortcut={<KeyHint icon={<CatalogIcon name="command-mac" size="lg" aria-hidden="true" />}>K</KeyHint>}
608609
/>
609610
);
611+
case "Capsule":
612+
return <CapsulePreview state="hover" />;
610613
case "StatusPill":
611614
return (
612615
<StatusPill leading={<CatalogIcon name="unselected" />}>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useState } from "react";
2+
import { Button, Capsule, Icon, IconButton, Stack, Tooltip } from "@openbitfun/ui";
3+
import { useI18n } from "../i18n";
4+
5+
export function CapsulePreview({ state = "default" }: { state?: string }) {
6+
const { t } = useI18n();
7+
const [removed, setRemoved] = useState<string[]>([]);
8+
const disabled = state === "disabled";
9+
return <Stack direction="horizontal" align="center" gap="3">
10+
{(["xs", "sm"] as const).filter(size => !removed.includes(size)).map(size => (
11+
<Capsule key={size} size={size} data-openbitfun-preview-state={state} action={(
12+
<Tooltip content={t("components.preview.close")} trigger="hover-focus">
13+
<IconButton aria-label={t("components.preview.close")} icon={<Icon name="xmark" />}
14+
disabled={disabled} onClick={() => setRemoved(current => [...current, size])} />
15+
</Tooltip>
16+
)}>
17+
<Button size={size} variant="outline" disabled={disabled}>{t("components.preview.modalProviderName")}</Button>
18+
</Capsule>
19+
))}
20+
{removed.length > 0 && <Button size="sm" onClick={() => setRemoved([])}>{t("components.preview.add")}</Button>}
21+
</Stack>;
22+
}

‎design-system/apps/design-lab/src/preview/FlowChatMockTools.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ export type MockToolScope = 'all' | ToolGroup;
99

1010
// Only grouping is Lab-owned. Names and component bindings come from production.
1111
const groupForTool = {
12-
get_goal: 'collaboration', create_goal: 'collaboration', update_goal: 'collaboration', AgentList: 'collaboration', AgentDelete: 'collaboration',
12+
get_goal: 'collaboration', create_goal: 'collaboration', update_goal: 'collaboration', AgentList: 'collaboration',
1313
SessionHistory: 'collaboration', analyze_image: 'files', GetTime: 'execution',
1414
ListMCPResources: 'execution', ReadMCPResource: 'execution', ListMCPPrompts: 'execution', GetMCPPrompt: 'execution',
1515
Worktree: 'files', PortForward: 'execution', ReviewPlatform: 'collaboration', FrontendWorkbench: 'web',
1616
FinalizeMiniApp: 'web', PublishMiniApp: 'web', PublishAppearance: 'web', Playbook: 'execution',
1717
Read: 'files', Write: 'files', Edit: 'files', Delete: 'files', Grep: 'files', Glob: 'files', LS: 'files',
1818
GetFileDiff: 'files', view_image: 'files',
19-
ListModels: 'execution', ControlHub: 'web', AgentInterrupt: 'collaboration',
19+
ListModels: 'execution', ControlHub: 'web',
2020
RunCode: 'execution', ExecCommand: 'execution', WriteStdin: 'execution', ExecControl: 'execution', Cron: 'execution', GetToolSpec: 'execution', Skill: 'execution',
2121
WebSearch: 'web', WebFetch: 'web', PageDeploy: 'web', PagePublish: 'web',
2222
AgentSpawn: 'collaboration', AgentSendInput: 'collaboration', AgentWait: 'collaboration', TodoWrite: 'collaboration',

‎design-system/apps/design-lab/src/preview/FlowChatPreviewRegistry.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LayersPlusIcon } from "@openbitfun/ui";
1+
import { Icon, LayersPlusIcon } from "@openbitfun/ui";
22
import { toolsForComponent } from '@openbitfun/flow-chat-presentation/registry';
33
import { semanticToolCardPreviews } from './SemanticToolCardPreview';
44
import { createScenarioClock, execScenarios, execSnapshot, type ExecToolName } from '@openbitfun/flow-chat-presentation/scenarios';
@@ -35,7 +35,6 @@ import {
3535
Monitor,
3636
MousePointer,
3737
Plus,
38-
Pencil,
3938
Rocket,
4039
Search,
4140
SearchCheck,
@@ -257,7 +256,7 @@ function ChatComposerPreview({ interactive, state }: PreviewProps) {
257256
/>
258257
<IconButton
259258
aria-label="Edit"
260-
icon={<Pencil />}
259+
icon={<Icon name="edit" />}
261260
size="xs"
262261
/>
263262
</ChatComposerQueueItemActions>

‎design-system/apps/design-lab/src/preview/componentPresentation.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type ComponentPresentation =
77
// These are website presentation choices. The public registry still owns the
88
// component inventory, properties and supported states.
99
const presentations: Record<string, ComponentPresentation> = {
10-
Button: "matrix", IconButton: "matrix",
10+
Button: "matrix", IconButton: "matrix", Capsule: "compact",
1111
Avatar: "compact", Checkbox: "compact", Radio: "compact", Switch: "compact",
1212
StatusPill: "compact", Spinner: "compact", LoadingState: "compact",
1313
LauncherButton: "compact", NumberBadge: "compact", KeyHint: "compact",

‎design-system/apps/design-lab/vite/component-detail-contract.test.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ test("IconButton preview exposes its icon-only presentation contract", async ()
201201
assert.ok(declaration);
202202
assert.deepEqual(
203203
[...declaration[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]),
204-
["annotation", "quiet", "outline", "fill", "primary"],
204+
["annotation", "quiet", "glass", "outline", "fill", "primary"],
205205
);
206206
assert.match(source, /data-component="icon-button"/);
207207
assert.match(source, /aria-label=\{t\(annotation \? "components\.preview\.fieldHelp" : "components\.preview\.listView"\)\}/);

‎design-system/apps/design-lab/vite/component-preview-render.test.mjs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ test("color and pattern directories expose one labelled panel while retaining ot
7575
}
7676
});
7777

78+
test("Capsule previews publish the glass action composition in every supported state", () => {
79+
const { html, preview } = render("Capsule");
80+
assert.match(preview, /data-openbitfun-component="capsule"/);
81+
assert.match(preview, /data-openbitfun-variant="glass"/);
82+
assert.match(preview, /data-openbitfun-shape="circle"/);
83+
assert.match(preview, /data-openbitfun-preview-state="disabled"/);
84+
assert.match(html, /import \{ Button, Capsule, Icon, IconButton \}/);
85+
assert.doesNotMatch(html, />component\.Capsule\.description</);
86+
});
87+
7888
test("Textarea opens with its editable, validation and disabled examples visible", () => {
7989
const { html, preview } = render("Textarea");
8090
assert.equal((preview.match(/data-openbitfun-component="textarea"/g) ?? []).length, 5);

0 commit comments

Comments
 (0)