Skip to content

fix(fields): percent/progress 单元格让数值优先于装饰条,窄 chip 不再裁掉数值 (objectstack#5066) - #3927

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-os5066-percent-chip-clip
Aug 9, 2026
Merged

fix(fields): percent/progress 单元格让数值优先于装饰条,窄 chip 不再裁掉数值 (objectstack#5066)#3927
yinlianghui merged 1 commit into
mainfrom
claude/issue-os5066-percent-chip-clip

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixesobjectstack-ai/objectstack#5066

现象与机制

percent 显示渲染器把「不可缩的 64px 装饰条」排在「可缩的数值文本」前面,于是在带裁剪的窄容器里牺牲了错的那一半。record:highlights 的 chip 是 basis-[9rem]、随 chip 数量收缩到 min-w-[7rem]、并用 truncate 裁剪:条(64px)加 gap-2(8px)吃掉内容盒,truncate 静默抹掉剩下的数值。

库里存 33.33,DOM 里是 33%,屏上只剩 3 —— issue 正文的量测:裁剪盒 79px、文本节点 32px、右溢出 25px,既无省略号,可访问名里也没有任何截断信号。这是静默错数,不是观感瑕疵;下游应用被迫把高亮项的渲染类型改写成 number 才能拿回数字,代价是丢掉 % 与进度条。

改法(issue 正文建议一)

按「数值是内容、条是装饰」反转收缩优先级,只动 PercentCellRenderer:

位置修前修后
行容器flex items-center gap-2flex min-w-0 items-center gap-2
条外层w-16 … shrink-0w-16 min-w-0 shrink …
数值 spantabular-nums whitespace-nowrapshrink-0 tabular-nums whitespace-nowrap

w-16 从「硬宽度」降级为「首选宽度」,容器一挤先把条挤没、数字完整保留;行容器补 min-w-0,否则负空间传不到条上。

刻意没有用建议里的 flex-1flex-1 会让条同时可以变宽,把每个宽表格单元里的条拉长 —— 那是一个本来没有 bug 的面。w-16 仍是上界(flex-grow 保持 0),宽容器渲染与改动前一致,只有收缩方向变了。

也没有叠加容器查询降级。 高亮条的 @container 在 section 上,而 chip 的实际宽度是「这条记录有几个高亮项」的函数,section 的宽度查询看不到它 —— 容器查询在这个形态下无法表达真正的判据,加了只是换一种猜。内容宽度低于约 40px 时条已缩为 0、数值和其他单行单元一样开始裁剪,这就是自闭环渲染器修法能到的下界;这条残余与整个 chip 侧的通用机制已单独记为 objectstack-ai/objectstack#6950(observation-class,未入队)。

同文件相邻的 number 渲染器未动(objectstack#5067 的面)。

测试

jsdom 没有布局引擎,像素测不了也不该造脆弱的像素测试,因此按 class 语义钉(与 objectui#3466 的 cell-truncation.test.tsx 同一做法),再叠 DOM 文本断言:

  • packages/fields/src/__tests__/PercentCellRenderer.test.tsx(7 例):正常宽度下条与数值都在;数值 shrink-0;条 w-16 min-w-0 shrinkshrink-0;条无 flex-1/grow/basis-(不许变宽);DOM 文本仍是完整格式化值(33% / precision: 233.33% / 分数存储 0.880% / progress 整数百分比)。
  • packages/plugin-detail/src/__tests__/RecordHighlightsRenderer.percentClip.test.tsx(4 例):从消费侧钉 authored metadata → chip → renderer 整条路径,含 7 个 chip 挤压最狠的形态;并钉住 chip 自己仍然 truncate —— 让位的是渲染器,不是 chip 的裁剪。

实跑证据:

vitest run packages/fields/ packages/plugin-detail/ --maxWorkers=2
Test Files 129 passed (129)
Tests 1492 passed (1492)
turbo run type-check --concurrency=2
Tasks: 78 successful, 78 total

反向验证(方向事先预判为红):把三处 class 还原成修前拼写后重跑两个新文件 —— 7 failed | 4 passed,失败点正是 expect(value).toHaveClass('shrink-0')expect(bar()).toHaveClass('w-16', 'min-w-0', 'shrink')。如实说明它证明了什么:class 名断言在修前拼写下必然红,所以这一跑证的是「测试真的接在被改代码上、查询非空转」,不是独立证明了布局后果 —— jsdom 评估不了布局。为把 class 名与真实 CSS 属性对上,另用仓内 Tailwind 4.3.3 编译过这几个 utility:w-16width: calc(var(--spacing) * 16)min-w-0min-width: 0pxshrinkflex-shrink: 1shrink-0flex-shrink: 0

没有浏览器实证:本容器里没有任何 Chromium 二进制(ms-playwright 缓存为空、系统无 chromium/chrome),所以 issue 里那 25px 溢出没有在改后重新量过一遍,也没有截图。这一格如实留白,而不是拿别的东西凑。

changeset:.changeset/percent-cell-value-over-bar-os5066.md(@object-ui/fields patch)。


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

…#5066)
percent 显示渲染器把「不可缩的 64px 装饰条」排在「可缩的数值文本」前面,
于是在带裁剪的窄容器里牺牲了错的那一半:`record:highlights` 的 chip 是
`basis-[9rem]`、随 chip 数量收缩到 `min-w-[7rem]`、并用 `truncate` 裁剪,
条(64px)加 gap(8px)吃掉内容盒,`truncate` 静默抹掉剩下的数值。库里存的
`33.33` 在 DOM 里是 `33%`,在屏上只剩 `3`(实测:裁剪盒 79px、文本节点 32px、
右溢出 25px),既无省略号,可访问名里也没有任何截断信号 —— 这是静默错数,
不是观感瑕疵。下游应用被迫把高亮项的渲染类型改写成 `number` 才能拿回数字,
代价是丢掉 `%` 与进度条。
现在按「数值是内容、条是装饰」反转优先级:数值 span 加 `shrink-0`,条的外层
改为 `min-w-0 shrink`,`w-16` 只作为它的首选宽度,容器一挤先把条挤没、数字
完整保留。行容器补 `min-w-0`,否则负空间传不到条上。
刻意没有用正文建议里的 `flex-1`:那会让条同时可以**变宽**,把每个宽表格单元
里的条拉长,改动一个本来没有 bug 的面。`w-16` 仍是上界,宽容器渲染与改动前
逐像素一致,只有收缩方向变了。内容宽度低于约 40px 时条已缩为 0,数值和其他
单行单元一样开始裁剪 —— 这就是「自闭环渲染器修法」能到的下界(chip 自身宽度
是记录高亮项数量的函数,高亮条上的 `@container` 看不到它)。
同文件相邻的 number 渲染器未动(objectstack#5067 的面)。
测试(jsdom 无布局引擎,故按 class 语义钉,同 objectui#3466 的 cell-truncation 做法):
- `packages/fields/src/__tests__/PercentCellRenderer.test.tsx`:数值 `shrink-0`、
条 `min-w-0 shrink` 且非 `shrink-0`、无 `flex-1`/`grow`、DOM 文本仍是完整
格式化值(33% / 33.33% / 0.8 → 80% / progress 整数百分比)。
- `packages/plugin-detail/src/__tests__/RecordHighlightsRenderer.percentClip.test.tsx`:
从消费侧钉整条 authored metadata → chip → renderer 路径,并钉住 chip 自己
仍然 `truncate`(让位的是渲染器,不是 chip 的裁剪)。
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 9, 2026 6:15am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-gCbjhVCv.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.66KB3.13KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)482.53KB106.37KB
core (index.js)2.96KB1.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)139.61KB35.99KB
fields (index.js)231.00KB56.76KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)61.04KB17.31KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)117.21KB30.27KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)236.63KB59.02KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)112.10KB27.10KB
plugin-gantt (index.js)162.55KB39.57KB
plugin-grid (index.js)187.63KB49.66KB
plugin-kanban (index.js)48.30KB13.28KB
plugin-list (index.js)105.12KB25.48KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.71KB1.34KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

验收通过(objectui 分片 PM,session session_01GTRjn8xBqp75dk7kFupVRt)objectstack-ai/objectstack#5066(v17)

实物核验:base 5e524950d → head 612d6d756,单提交 4 文件;核心 diff 极小且正确 —— 数值 span shrink-0(内容不可牺牲),条去 shrink-0min-w-0 shrink 且保 w-16 上界。CI 亲读终态:20 检查全 completed、0 失败。

验收要点:

  • 偏离正文字面 flex-1 的裁量采纳:flex-1 会让宽表格单元的条被拉长(一个本没有 bug 的面),w-16 作首选宽度 + 可缩正确地只改窄容器行为。
  • 不叠加容器查询的论证成立:chip 宽度是高亮项个数的函数,section 级 @container 表达不了真正判据。
  • Tailwind 4 JS API 现编译核出 class → CSS 属性映射,补上了 jsdom 无布局引擎的证明缺口;反向验证的证明力边界(证明测试接线、非独立证明布局)自我declared清楚,诚实。
  • 容器无 Chromium、25px 溢出未重量测一格留白如实上报 —— 接受,残余下界与通用面已由 #6950 承接。

范围外 finding objectstack#6950(chip truncate 对多元素渲染器的静默裁剪通用面)立单规范,冻结期不派,归后续分诊。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 06:24
@yinlianghui
yinlianghui added this pull request to the merge queueAug 9, 2026
Merged via the queue into main with commit f4b97c8Aug 9, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-os5066-percent-chip-clip branch August 9, 2026 06:24
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console: percent display renderer's fixed-width progress bar clips the value in a record-highlights chip (33% renders as 3)

2 participants

@yinlianghui@claude