Repository navigation
Commit 5cc31c7
fix(plugin-list): fold the Display popover's layout segments to icons when they would not fit (#26696)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbX9aieGr9vBEvWMpCbnjnsG)_
Fixes #26637
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:列表「显示」弹层的「布局」一行,在分段按钮放不下时自动折叠为仅图标,当前选中的那一段保留文字;弹层宽度不变。
- **为什么改**:四种布局时「表格 / 看板 / 日历 /
时间线」每个标签都被挤成两行,英文更长;裁决要求按实测宽度折叠,不按数量或断点,所有语言一致。
- **风险与回滚**:只动 `ViewSettingsPopover.tsx` 一个组件,不改任何元数据或存储;回滚即 revert 本
PR。
-
**先看**:`ui/packages/plugin-list/src/components/ViewSettingsPopover.tsx`
的 `LayoutSlot`(测量时临时撤下折叠属性、读宽、再放回)。
- **两分钟验证**:中文环境打开有四种布局的对象列表,点「显示」:布局行一行高,只有当前布局带文字,其余是图标,悬停显示名称。
- **需要你做的**:无,席位落地。
## What changes
`ui/packages/plugin-list/src/components/ViewSettingsPopover.tsx` only.
The Layout row's control now sits in a `LayoutSlot`:
- **Measured fold.** After every render and whenever the slot resizes,
the slot compares the width its segments need with every label shown
(the children's `scrollWidth`) against its own `clientWidth`. When they
would not fit it sets `data-layout-fold="icons"`, and one class hides
the words of every unselected `role="tab"`. The selected segment keeps
its label. No count and no breakpoint enters the decision, so it holds
in every locale.
- **Measurement lifts the fold.** A folded row's hidden labels add
nothing to its width, so each measurement removes the attribute, reads,
and puts it back in the same synchronous task, before the browser
paints. Without that step the row would unfold, refold and loop (the
second ablation below shows React's update-depth error).
- **Names and tooltips stay.** A folded segment keeps the `aria-label`
and `title` that `ViewSwitcherDropdown` already gives it, so it is still
announced and still named on hover. No locale key was added.
- **No wrapping.** Tabs in the slot are `whitespace-nowrap` and
`shrink-0`, and the row's title no longer shrinks.
- **The popover keeps its width** (`w-80`); the ruling forbids widening
it.
The fold reads the shape `ViewSwitcherDropdown` draws (a
`role="tablist"` whose tabs carry their name in `aria-label` and `title`
and their words in a direct child span). That contract is written on the
`layout` prop's doc comment. `ListView.tsx` and `ViewSwitcher.tsx` are
untouched.
## Tests
`ui/packages/plugin-list/src/__tests__/ViewSettingsPopover.layoutFold.test.tsx`,
5 cases. jsdom has no layout, so the test models the console's widths
(segment padding, icon, gap, 12 px per CJK character and about 7 px per
Latin one at `text-xs`) and reads the fold the way the class does:
- zh-CN, the walked row 「布局 | 表格 | 看板 | 日历 | 时间线」: folds, the folded row
fits the slot, every tab keeps its accessible name and `title`, only 表格
shows its words, and no width was read while the fold was on.
- en, the same four layouts: folds.
- Five layouts at the popover width: the fold engages and the row fits.
- Measured, not counted: the same five layouts in a 600 px slot do not
fold; two layouts at the popover width do not fold.
- A row that fits again unfolds, and every label shows.
## Acceptance notes
- The card says the popover is 240 px. On `main` it is `w-80`, 320 px;
measured 320 in Chromium. The row still did not fit at 320: the slot
beside the title is 258 px in zh-CN and 244 px in en.
- Screenshots (Chromium, 1149 × 507 viewport, the real components and
the console's compiled Tailwind sheet) are attached to the card thread:
zh-CN, en, ja and de with four layouts all fold to one 49 px row with
the selected label kept; en with Timeline selected keeps "Timeline";
zh-CN with two layouts keeps both labels.
- Folding relies on the switcher's DOM shape rather than a prop, because
the order keeps the fold inside the popover and `ListView.tsx` is being
edited by another card. Passing an explicit icons-only flag through the
`layout` render prop would be the next step if another host ever draws
the row differently.
## Verification
All runs on head `ecb5f58f` unless noted, ui suites from `ui/` under
`scripts/pm/os-verify-lock.sh`.
<details><summary>plugin-list suite: 164 files, 1650 tests,
green</summary>
```
> vitest run --root ../.. packages/plugin-list/ --maxWorkers=2
Test Files 164 passed (164)
Tests 1650 passed (1650)
os-verify-lock: VERDICT command-exit 0 · held the lock 530s
```
</details>
<details><summary>plugin-list type-check: green (tsconfig.test.json
lists the new test)</summary>
```
pnpm exec turbo run type-check --filter=@object-ui/plugin-list --concurrency=2
> tsc --noEmit && tsc -p tsconfig.test.json
Tasks: 49 successful, 49 total
os-verify-lock: VERDICT command-exit 0
```
</details>
<details><summary>Ablations: both go red, both restored to
HEAD</summary>
Through `scripts/ablation-replace.mjs` (anchor hit 1 → 0, blob restored
to HEAD, `git diff HEAD` empty), on `dfbd4bf5`:
1. Render the layout without `LayoutSlot`: 4 failed, 1 passed (the "does
not fold" case stays green, as it should).
2. Delete the line that lifts the fold before measuring: 4 failed with
`Maximum update depth exceeded`.
</details>
<details><summary>Chromium check of the compiled sheet</summary>
The console's `index.css` compiled through `@tailwindcss/postcss` emits
the fold rule:
```
.\[\&\[data-layout-fold\]_\[role\=tab\]\[aria-selected\=false\]\>span\]\:hidden[data-layout-fold] [role=tab][aria-selected=false] > span { display: none; }
```
| locale | layouts | fold | slot px | row px | selected keeps its label
| every tab named + titled |
|---|---|---|---|---|---|---|
| zh-CN | 4 | icons | 258 | 49 | 表格 | yes |
| en | 4 | icons | 244 | 49 | Grid | yes |
| en | 4, Timeline picked | icons | 244 | 49 | Timeline | yes |
| ja | 4 | icons | 221 | 49 | グリッド | yes |
| de | 4 | icons | 244 | 49 | Raster | yes |
| zh-CN | 2 | none | 258 | 49 | both labels shown | yes |
Popover width 320 px in every run.
</details>
<details><summary>Derived gates: 34 of 34 exit 0, reconciled with
--ran</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on `ecb5f58f` derived 34 commands; each ran
with its exit code captured before any pipe (`GITHUB_EVENT_PATH`
pointing at this body for the PR-body gates). `--ran` answers: "34
derived, 34 run, 0 NOT-MEASURED, 0 UNRUN".
`check:partof-closing-keyword` with this body: green.
`check:closing-target-claim` needs the PR number, so CI's
`pr-body-guards` runs it.
```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
The type-check lanes (`Type Check UI`, the workspace lanes) and the
wide-population families are CI's.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jUfYgopbwA4FqJngRV)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2eefc8a commit 5cc31c7
3 files changed
Lines changed: 309 additions & 2 deletions
File tree
- .changeset
- ui/packages/plugin-list/src
- __tests__
- components
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
74 | 79 | | |
75 | 80 | | |
76 | 81 | | |
| |||
248 | 253 | | |
249 | 254 | | |
250 | 255 | | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
251 | 319 | | |
252 | 320 | | |
253 | 321 | | |
| |||
321 | 389 | | |
322 | 390 | | |
323 | 391 | | |
324 | | - | |
| 392 | + | |
325 | 393 | | |
326 | 394 | | |
327 | | - | |
| 395 | + | |
328 | 396 | | |
329 | 397 | | |
330 | 398 | | |
| |||
0 commit comments