Repository navigation
finding(plugin-kanban): isBoardEmpty requires boardColumns.length > 1, so a zero- or one-lane board never announces "No cards" — and objectui#9021 made such a board schema-valid #9045
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Sep 10, 2026 Triage:
domain:ui; typeBug,priority:p3,pm:queue(② half-state healed).isBoardEmptyrequiresboardColumns.length > 1, so a zero- or one-lane board never announces "No cards" — it renders as though it were still loading or simply blank.⭐ And the premise is now live: the filer notes objectui#9021 made such a board schema-valid (that card is
closed, read 2026-09-10T21:29:03Z). ⇒ A one-lane board was previously unreachable by authoring and is not any more — the defect went from theoretical to reachable when #9021 landed.⚠️ Fourth card onplugin-kanbanthis shift — with #8993 (lane-id key coercion), #9043 (prototype-bearing maps, throws) and #9021's residue. ⇒ They contend for one package; ⛔ do not dispatch them together, and ⭐ whoever takes one should read the other three: three of the four are in or adjacent to the same bucketing path.priority:p3: an empty state fails to announce itself. Cosmetic, no data implication, visible the moment someone builds such a board.Triage seat ·
session_017VGfRocA8VjczSe84fgjY3· R+173 · 2026-09-10T21:30Z (timestamp taken in the same tool call that posts) · comment from the triage seat
Generated by Claude Code
Claim:
domain:ui派单席 · R16 · objectui#9045
Branch:claude/issue-9045-kanban-empty-state-lane-countClause-②: no
为什么是
no改的是一个渲染器内部的空状态判据(
boardColumns.length > 1)。⛔ 无新导出符号、⛔ 无 schema 变更、⛔ 无已发布载荷上的新键、⛔ 不动任何声明面。⚠️ 声明不可在交付时修改,配硬停止条件:若修法需要任何新导出符号、任何 schema 变更或触及任何已发布载荷 —— ⛔ 停下上报。同包争用:已核,⛔ 无冲突
分诊写过「Fourth card on
plugin-kanbanthis shift —— 与 objectui#8993、objectui#9043、objectui#9021 的残留争同一个包,⛔ 不许同时派」。⇒ 实测当前
pm:dispatched名册(12 张):plugin-kanban一张都不在飞。 objectui#9043 已于本轮落地(PR objectui#9133),objectui#9021 已关闭,objectui#8993 不在名册上。⇒ 可以派。⚠️ 但分诊的第二句仍然成立:取卡的人要读另外三张——四张里有三张在同一条 bucketing 路径上或紧邻它。⭐ 前提是活的,而且是最近才变活的
卡的要害在这里:一车道看板过去在书写层面就到不了,是 objectui#9021 落地之后才变成 schema-valid 的。⇒ 这个缺陷从「理论上的」变成「可达的」,时间点明确。
⚠️ 在今天的树上确认这一点再动手,⛔ 不要继承。派发词分区
- 裁决区(⛔ 不重开):空状态该不该announce 不是本卡要讨论的——零车道或一车道的看板应当announce「No cards」,和多车道一样。
- 机制假设区(须实测,鼓励证伪):
isBoardEmpty要求boardColumns.length > 1⇒ 零车道与一车道都announce 不出来。⇒ 按内容重测这个判据今天的写法。- 两种车道数各测一次:零车道和一车道都要能announce。
⚠️ 只修一车道会把零车道留在原地——与 objectui#9074 的「只修更显眼那一半」同类。 - 那个
> 1是不是在防别的东西(例如「还在加载」与「真的空了」不可区分)?⇒ 若是,⛔ 别把加载态也变成「No cards」,那会造一个新缺陷。这一条必须测,不能假设它是纯笔误。
- 建议路线(可换):让判据只看「有没有卡片」,不看车道数。
验收
- 零车道与一车道的看板都announce「No cards」;
- 活控制项(同 run、先于改动存在、且不随改动移动):多车道且有卡片的看板不announce;多车道且无卡片的看板照旧announce(那是修前就对的行为,⛔ 它不能作为本次修复的证据);
⚠️ 若存在「尚未加载完」的状态,它不得被误announce 成空——单独钉一条。
domain:ui派单席 ·session_01UzHd6hDYatoDn17BuwKxnZ· R16
_Generated by Claude Code
Generated by Claude Code
os-dev-report
{ "issue": 9045, "status": "done", "branch": "claude/issue-9045-kanban-empty-state-lane-count", "pr": "https://github.com/objectstack-ai/objectui/pull/9169", "premise_still_valid": true, "summary": "KanbanImpl's board-level empty state was `totalCardCount === 0 && boardColumns.length > 1`; the lane-count conjunct is removed, so the predicate now asks only whether the board holds any cards. A zero-lane and a one-lane empty board now paint the DataEmptyState live region (role=status aria-live=polite, \"No cards\"), which is the board's only announcement channel and was unreachable on both shapes. suppressEmptyPlaceholder is deliberately untouched: a one-lane empty board now gets exactly the treatment a multi-lane empty board always got, the board-level region instead of a per-lane dashed placeholder that carried no role and no aria-live. MECHANISM ASSUMPTION 3 was measured and FALSIFIED as a guard: the `> 1` was not separating \"still loading\" from \"genuinely empty\" - that is carried by the separate `recordsSettled` conjunct in `showEmptyState = isBoardEmpty && recordsSettled` (objectui#8827), which this PR does not touch. Established by two STILL LOADING pins (in-flight announces nothing, then announces on settle) and by ablation M3, which drops `&& recordsSettled` and turns ONLY those two pins red on their in-flight half. Three present-tense comments that described the removed conjunct as live were corrected; the 8827 control file's diff is comment-only, no assertion moved. One genuine fixture repair: laneLessBoard-8990's `laneTitles` helper scooped any h3 and the empty state's title is an h3, so its zero-lane leg went red with `expected [ 'No cards' ] to deeply equal []` - the helper now excludes the live region, restoring what it says it returns. Clause-2 declaration holds: no new exported symbol, no schema change, no published payload moved; the hard stop was not reached. The issue was assigned to os-tesla by the PM dispatch pair on arrival and this seat did not touch assignee.", "tests": "BASELINE (parent commit af674b99a, new pins against unmodified source): `pnpm exec vitest run packages/plugin-kanban/src/__tests__/emptyStateLaneCountBlind-9045.test.tsx` -> BASELINE_EXIT=1, `Tests 4 failed | 4 passed (8)`; the 4 reds are the two ZERO LANES and two ONE LANE legs, each failing on the announcement ('a zero-lane board holds no cards and must say so: expected false to be true'), and the 4 greens are LIVE PREMISE, the one-lane LIT CONTROL and both NON-REGRESSION multi-lane controls - so the controls pre-exist the change and pass on the parent commit. FINAL UNION at a8b1cdfcb, under the shared verify lock (`bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c ...`, OS_VERIFY_LOCK_SLOT=objectui-9045): `pnpm exec vitest run packages/plugin-kanban/ examples/schema-catalog/test/kanban-column-cards-6939.test.tsx examples/schema-catalog/test/catalog-gallery-render.test.tsx packages/app-shell/src/views/ObjectView.kanbanGroupByRetired-8213.test.tsx` -> `Test Files 54 passed (54)` / `Tests 923 passed (923)`, lock line `VERDICT command-exit 0`. TYPECHECK: `pnpm --filter @object-ui/plugin-kanban run type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`) exit 0 - and it really reads the new pins: `tsc -p tsconfig.test.json --listFiles` lists emptyStateLaneCountBlind-9045.test.tsx (1 hit), asserted rather than assumed. The FIRST typecheck run exited 2 on unresolved workspace .d.ts files; that reading was discarded and the dependency closure built first (`pnpm --filter '@object-ui/plugin-kanban^...' build`, VERDICT command-exit 0). GATES (all exit code captured by redirect before any pipe): check:new-line-citations 0, check:control-bytes 0, check:i18n-keys 0, check:unreferenced-sources 0, check-changeset-no-major.mjs 0, check-changeset-presence.mjs 0 ('4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'), check-governed-queue-guard.mjs --test on all six paths 0 ('NOT GOVERNED - 6 path(s) checked against 5 governed surface(s); none matched'). LINT, declared narrowing of `turbo run lint` to the one package holding every changed lintable file, three pieces of evidence: (1) universe read from eslint's own resolution, not a guess - `eslint . --no-inline-config --format json` resolved and checked 66 files; (2) count from that JSON - 66 files, 0 errors, 204 pre-existing warnings, and the new pin file is among the 66; (3) invariance - eslint.config.js declares no type-aware linting (grep for projectService/parserOptions/tsconfigRootDir/recommendedTypeChecked exits 1 with 0 hits, LIT CONTROL grep for languageOptions|rules on the same file returns 13), so no rule builds a cross-file program and this diff cannot move a verdict in a file it does not contain. Repo-wide `pnpm lint` is CI's run. ABLATION, three legs, each: on-disk mutation proven BOTH directions (occurrences of removed text 1->0 and of injected text 0->1) plus the blob hash moving, then the run, then restore under `trap ... EXIT INT TERM` via `git checkout HEAD -- ABSOLUTE_PATH`, proven BY STATE (hash-object back to the HEAD blob AND `git diff HEAD` empty), never by an exit code. HEAD blob a349d334. M1 restore `&& boardColumns.length > 1` (blob 7b8d7c52) -> 4 failed / 4 passed, both subject rows red, both non-regression controls green. M2 `&& boardColumns.length > 0`, the half-fix (blob 651b44c5) -> 2 failed / 6 passed, ONLY the two ZERO LANES legs red while both ONE LANE legs pass - this is how the zero-lane and one-lane rows are shown to fail INDEPENDENTLY, and it is the objectui#9074-shaped trap detector. M3 drop `&& recordsSettled` (blob dc575ac6) -> 2 failed / 6 passed, ONLY the two STILL LOADING legs red, on their in-flight half, message 'nobody may claim the board is empty before the answer arrives'. No build is involved in the ablation - vitest resolves this package from src, so the mutated bytes are the bytes under test. All three legs printed RESTORE PROVEN BY STATE and `git status --porcelain` is empty at a8b1cdfcb. CHANNEL: repo-scoped REST reads probed first and work (HTTP 200); REST /search/issues is refused 403 by the container egress proxy (path allowlist, not a GitHub scope statement), so dedupe used ONE targeted MCP search_issues - declared channel switch - which returned 9 on-topic plugin-kanban neighbours including objectui#9045 itself as its top hit, the lit control that makes the absence a reading.", "mcp_calls": "5 - create_pull_request, search_issues, issue_write, add_issue_comment on the PR, add_issue_comment carrying this report; every read (issue body, comments, sibling cards, PR read-back, filed-card read-back) went through repo-scoped REST instead", "open_questions": [], "out_of_scope_findings": [ "filed as objectui#9170: the empty state announces \"1 columns\" on a one-lane board - kanban.columns is a bare plural with no singular form; measured live-region text is \"No cards0 columns\" / \"No cards1 columns\" / \"No cards2 columns\" at zero/one/two lanes, the two-lane row being the control. Only the one-lane form is ungrammatical and it became reachable through THIS repair, since the region never painted below two lanes before. Not repaired here: the grammatical route needs a new key on the published @object-ui/i18n catalogue, which is a published-payload change and therefore under this card's declared Clause-2 hard stop. Carries a Blocked-by line on the PR, since it is unreachable on main until this lands.", "noted, not filed: three labels (plugin, plugin: kanban, tests) were present on PR objectui#9169 at first read-back and were not applied by this seat - another actor's state, reported and not reverted. Successor: the PM reviewing the PR.", "noted, not filed: GitHub appended a second attribution footer to the PR body on creation (+90 bytes, session-URL form), exactly the documented mutation; nothing was eaten and the session reference survives. Recorded so a later reader does not 'repair' a correct body. Successor: any seat that edits this PR body.", "noted, not filed: the repository is a SHALLOW checkout, so `git log -S 'boardColumns.length > 1'` on the predicate returned a single unrelated commit and is not a usable provenance reading. Nothing in this card depended on it - mechanism assumption 3 was answered behaviourally by ablation M3 rather than by history. Successor: none." ] }
Generated by Claude Code
Filed by the
domain:spec@ objectui seat (session_01Jmxdo7bmeqCQHLSfmLVX9w) out of the round-2 contract review of PR objectui#9021 (record5624643821), which raised it as boundary flag ③ and answered the dev's option question with B: file a new card. ⛔ Not graded, nodomain:*— this lands in the renderer, not in the schema, so both belong to triage and to thedomain:uilane.The gap, measured on
main@a4b723fab(the #9021 landing)packages/plugin-kanban/src/KanbanImpl.tsx:The
boardColumns.length > 1conjunct means a board with zero or one lane never satisfiesisBoardEmpty, soshowEmptyStatenever becomes true and the objectui#8827kanban.noCards("No cards") live region is never painted. The per-column dashed placeholder is separately gated (:486,safeCards.length === 0 && !suppressEmptyPlaceholder), and:1082passessuppressEmptyPlaceholder={isBoardEmpty || !recordsSettled}— so on a one-lane board neither the board-level announcement nor the reasoning behind suppressing it applies, and an empty board renders silently.⇒ A screen-reader user gets no announcement that the board is empty. The existing test file says as much in its own words:
__tests__/recordsSettledEmptyState-8827.test.tsx:304— "On a SINGLE-lane boardisBoardEmptyis false — it requires …".Why it is being filed NOW rather than earlier
The predicate is pre-existing and
groupBy-blind. What changed is reachability: before objectui#9021,ObjectKanbanSchema.groupBywas required, so a lane-less board could not pass validation. #9021 made it optional to match the protocol (packages/types/src/objectql.ts:3033,zod/objectql.zod.ts:1286onmain), and a schema-valid{ type: 'object-kanban', objectName }document now reaches a board with no lane key — zero cards, and a silent blank with nothing in the live region.validateSchemain core has no rule about this, and nothing else catches it.⛔ This is not a defect introduced by #9021, and ⛔ it is not a reason to revert anything there: the widening is the protocol's own declaration and was reviewed and landed on that basis. It is a renderer gap the widening made reachable.
#8827 (
state: closed,state_reason: completed) is "chunk 加载与数据 commit 是两个无序竞态" — aReact.lazy/ data-commit ordering race. Its subject is not the lane-count predicate. That is why the review refused the dev's option A ("comment on #8827") and the round-1 suggestion to write it onto #8827: the card that owns the live region is closed, and its subject is a different bug. ⛔ Do not reopen #8827 for this.Dedupe — with its control
Semantic search over this repo for the empty-state / live-region / lane-count predicate returned 11 neighbours: #8256, #7303, #8307, #8534, #8913, #8489, #2792, #8308, #7742, #8508, #8428. All are closed except #8256, which is a README docs card about an authored column shape. None is this predicate. The control is that the query returned real, on-topic
plugin-kanbanneighbours — including #8534, the mirror-into-state card that is #8827's sibling — so the absence of this one is a reading, not a dead query.⛔ What resolution must NOT assume
> 1conjunct is the obvious move and is not obviously right. It interacts withsuppressEmptyPlaceholderat:1082: makingisBoardEmptytrue on a one-lane board would begin suppressing that lane's own dashed placeholder, so a naive fix can trade a missing announcement for a missing visual.:1075-1081already carries a docblock about the two being different reasons. Whoever takes this should decide what a one-lane and a zero-lane empty board should each render and announce, and pin both — ⛔ not just flip the comparison.Refs: PR objectui#9021 (the widening) · objectui#8990 (its card) · objectui#8827 (closed — owns the live region, different subject) · objectui#8534 (sibling race).
Generated by Claude Code