Repository navigation
Commit 94bab5a
fix(ui): lay the stage path and kanban lanes out by the declared option kind (#26689)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbQFYwXjGdbCRo4TV4yeyKWa)_
Fixes #26612
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:阶段条(record:path)和看板现在按选项声明的生命周期
`kind`(open/won/lost)排布。字段一旦声明了 kind,阶段条不再按 `closed_won`/`closed_lost`
拼写猜,未声明 kind 的选项按 spec 读作 open;阶段按 kind 排:open 步骤在前,won 终点收尾,lost
单独放在右侧分组。看板从选项派生的列按 open → won → lost 排,lost 列默认折叠,只显示列名和卡片数,可点开。
- **为什么改**:lost 选项写在列表中间时,阶段条把它后面的步骤和 won 终点全部挤进 lost 分组;看板完全不读
kind,lost 列夹在进行中的列之间。分诊(6440222656)要求声明 kind 后不再保留按名字猜的回退。
- **风险与回滚**:低。只改两个渲染器,无新键、无公开导出变化。没声明 kind
的字段,阶段条照旧按拼写猜、照旧按位置排,只多一条开发构建下的 console 警告;看板对没声明 kind 的字段和作者显式写的
`columns` 完全不变。回滚即 revert 本 PR。
- **先看**:`ui/packages/plugin-detail/src/renderers/record-path.tsx` 的
`classify` / `placed` / `stageTerminals`
三段;`ui/packages/plugin-kanban/src/kanbanLaneLifecycle.ts`;两个新测试文件。
- **两分钟验证**:`cd ui && pnpm exec vitest run
packages/plugin-detail/src/renderers/__tests__/record-path.declaredKindLayout-26612.test.tsx
packages/plugin-kanban/src/ObjectKanban.laneLifecycle-26612.test.tsx`,预期
13 个用例全绿。
- **需要你做的**:无,席位落地。
## What changes
`SelectOption.kind` (`open | won | lost`, objectstack#23809) is
published, and the spec reads an absent kind as `open`. On `origin/main`
(`a76f811e`):
- `record:path` read a declared kind (objectui#12907) but still
classified every option WITHOUT one by its spelling, and laid stages out
by position: everything from the first lost-class stage on went into the
separated group. A lost option authored mid-list pulled the open and won
options after it out of the forward path, and the won option there lost
its terminus mark.
- The kanban read no kind at all: lanes derived from the lane field's
options ran in authored order, all expanded.
- Nothing warned when the spelling fallback decided a stage.
Now:
**`record:path`**
(`ui/packages/plugin-detail/src/renderers/record-path.tsx`)
- Once the status field declares a kind on any option, `classify` reads
only the stage's explicit `terminal` and the declared kind; an option
without a kind reads as `open`. ⛔ No name-based fallback once a kind is
declared (triage 6440222656).
- With declared kinds the stages are laid out by kind, a stable
partition: open steps in authored order, then the won option(s) as the
forward terminus, then the lost option(s) in the separated group. Both
rows (desktop and mobile) index the one laid-out array, as before.
- Every declared won option is a terminus. The last-forward-stage
restriction stays for the spelling guess only, where it guards `完成`
mid-path.
- A field that declares no kind keeps the spelling fallback and its
positional layout unchanged. A dev build (`NODE_ENV !== 'production'`,
the package's established guard spelling) warns once per object, field
and guessed set, naming the object, the field and the guessed stage
values, and saying to declare `kind`. Nothing warns when the fallback
guessed nothing.
**Kanban** (`ui/packages/plugin-kanban/src/ObjectKanban.tsx`, new
module-internal `kanbanLaneLifecycle.ts`)
- Lanes derived from the lane field's options go through
`lanesFromOptions`: when any option declares a kind, they run open → won
→ lost (stable, authored order within each class) and every lost lane
carries `collapsed: true`, so `KanbanImpl` draws it as a spine with its
count and a disclosure the viewer can open (`KanbanColumn.collapsed`,
honoured since objectui#9628).
- The kind is read off the raw options by value, so the i18n hop cannot
drop it.
- Authored `columns`, data-derived lanes and fields with no declared
kind are untouched; the board guesses nothing from spelling.
No new key, prop or export: `lanesFromOptions` is not re-exported from
the package entry, and the helper `declaredStageKinds` in
`synth/buildDefaultPageSchema.ts` is reused unchanged.
`ui/packages/app-shell` needed no change: the kanban reads the object
definition through `useSettledSchema` and the path through the record
context's `objectSchema`, both unnormalised, so `kind` reaches both
readers.
## Tests
- New
`ui/packages/plugin-detail/src/renderers/__tests__/record-path.declaredKindLayout-26612.test.tsx`
(7 tests): (a) a lost option authored mid-list sits apart with the open
steps and won terminus forward, progress follows the laid-out order, and
a lost record marks the lost stage current; (b) an option without a kind
on a field that declares kinds reads as open though spelled
`closed_lost`; (c) two declared won options are both termini; (d) the
fallback warns once naming field and object, re-render does not repeat
it, a declaring field never warns, and a fallback that guessed nothing
does not warn.
- New
`ui/packages/plugin-kanban/src/ObjectKanban.laneLifecycle-26612.test.tsx`
(6 tests): `lanesFromOptions` order and collapse, no-kind control; the
rendered board's lane order; the lost lane collapsed with its count,
opening on click; open and won lanes carry no disclosure; CONTROL
authored `columns` keep their order and state.
- The existing record-path suites (objectui#12907 declared-kind pins and
the spelling-fallback controls included) pass unchanged.
<details><summary>Ablations (one-off, not kept; each through
scripts/ablation-replace.mjs, restored to the HEAD blob with git diff
HEAD empty)</summary>
The tests import the renderers from source (`../../index`,
`./kanbanLaneLifecycle`), so no `dist/` sits between mutation and run.
| # | Mutation | Result |
|---|---|---|
| A1 | `if (declaredKinds) return …` → only for options that declare one
(the old per-option gate) | Tests 1 failed / 6 passed: (b) |
| A2 | `placed = declaredKinds ? sort : classified` → never sort | 3
failed / 4 passed: (a) ×2, (c) |
| A3 | every declared won → last-forward-stage only | 1 failed / 6
passed: (c) |
| A4 | the warning effect always returns early | 1 failed / 6 passed:
(d) first case. A first attempt appended to the anchor line and was
refused by the tool as a no-op (anchor count 1 → 1); rerun with a
distinct replacement. |
| A5 | `lanesFromOptions` ignores kinds | 3 failed / 3 passed in the
kanban file |
</details>
## Acceptance notes
- **A lost record's path.** The card asks that a record in a lost state
show "the path stopped at its last open step". The record carries only
its current value, not the open step it left, so this PR draws what is
known: the lost stage current, with ✗, apart from the rail, and the
forward steps untravelled (unchanged from main). Drawing the last open
step needs a stage-history read; reported to the seat as an open
question, not built here.
- **Screenshots** of a HotCRM lead (未通过) and opportunity (Closed Lost)
at 1280 px are not taken: HotCRM declares its kinds in its own card, and
this container runs no HotCRM. The DOM pins above assert the same layout
through `data-stage-terminal`, `data-stage-state` and the two desktop
groups.
- **Lane paging.** The paged-lane key (`pagedLaneIdsKey`) still lists
the option values in authored order; it keys the per-lane fetch, not the
layout, so the order there does not matter.
- `#26581` (same seat) also edits `record-path.tsx` and rebases after
this lands.
## Verification
Readings at head `ba8cc73a` unless named.
<details><summary>Touched packages: @object-ui/plugin-detail,
@object-ui/plugin-kanban</summary>
- `bash scripts/pm/os-verify-lock.sh -c 'cd ui && pnpm exec vitest run
--maxWorkers=2 packages/plugin-detail/ packages/plugin-kanban/'` (after
the `^build` closure): Test Files 394 passed, 1 skipped (395); Tests
3441 passed, 18 skipped (3459); VERDICT command-exit 0.
- The two new files alone with every existing `record-path` suite: Test
Files 12 passed (12), Tests 90 passed (90).
- `pnpm exec turbo run type-check --filter=@object-ui/plugin-detail
--filter=@object-ui/plugin-kanban --concurrency=2` (builds the `^build`
closure, 49 tasks): plugin-kanban green; plugin-detail red on two TS7006
in the new test's spy reader, fixed in `ba8cc73a`, then `pnpm run
type-check` in `ui/packages/plugin-detail` (`tsc --noEmit && tsc -p
tsconfig.test.json`, tests covered): exit 0.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 34 commands from 6 changed paths
(merge base `a76f811e`). Each ran with its exit code captured before any
pipe; all 34 exit 0. The PR-body gates (`check-adr-0087-registration
--base`, `check-changeset-no-major --base`,
`check-closing-keyword-parity`) read this body through
`GITHUB_EVENT_PATH`; `check:type-check-debt` and `pnpm --dir ui exec
vitest run scripts/` ran under the lock, the latter with
`--maxWorkers=2` added (Test Files 178 passed, 2 skipped; Tests 5065
passed, 2 skipped). Reconciliation: `--ran` answered "34 derived
famil(ies) accounted for — 34 run, 0 NOT-MEASURED (a DERIVED zero — all
34 recorded an exit code and none of them is 3)". Also `node
ui/scripts/check-new-cross-file-line-citations.mjs`: 0 new citations,
exit 0. `origin/main` moved 17 commits since the base, none under
`ui/packages/plugin-detail`, `plugin-kanban`, `react` or `types`, and
`git merge-tree` against it is clean.
</details>
Dogfood battery: not owed (no `!` in the title, no `(narrowing)` arm).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jSjdwziSeaPzNdLFi1)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1685a06 commit 94bab5a
6 files changed
Lines changed: 555 additions & 20 deletions
File tree
- .changeset
- ui/packages
- plugin-detail/src/renderers
- __tests__
- plugin-kanban/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 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 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
72 | 90 | | |
73 | 91 | | |
74 | 92 | | |
| |||
243 | 261 | | |
244 | 262 | | |
245 | 263 | | |
246 | | - | |
247 | | - | |
248 | | - | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
249 | 274 | | |
250 | 275 | | |
251 | | - | |
252 | | - | |
253 | | - | |
254 | | - | |
| 276 | + | |
255 | 277 | | |
256 | 278 | | |
257 | 279 | | |
258 | 280 | | |
259 | 281 | | |
260 | | - | |
| 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 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
261 | 325 | | |
262 | 326 | | |
263 | 327 | | |
264 | 328 | | |
265 | 329 | | |
266 | | - | |
267 | | - | |
| 330 | + | |
| 331 | + | |
268 | 332 | | |
269 | 333 | | |
270 | 334 | | |
| |||
293 | 357 | | |
294 | 358 | | |
295 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
296 | 365 | | |
297 | 366 | | |
298 | | - | |
| 367 | + | |
299 | 368 | | |
300 | 369 | | |
301 | | - | |
| 370 | + | |
302 | 371 | | |
303 | 372 | | |
304 | 373 | | |
305 | | - | |
| 374 | + | |
306 | 375 | | |
307 | 376 | | |
308 | 377 | | |
| |||
508 | 577 | | |
509 | 578 | | |
510 | 579 | | |
511 | | - | |
| 580 | + | |
512 | 581 | | |
513 | 582 | | |
514 | 583 | | |
| |||
0 commit comments