Repository navigation
feat(spec,lint): a kanban board chooses which lanes it draws, and in what order (kanban.lanes) - #28196
Conversation
….lanes Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxL3MYcTjfshb8eeK3
…ield, or listed twice Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxL3MYcTjfshb8eeK3
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxL3MYcTjfshb8eeK3
…et covers lint Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxL3MYcTjfshb8eeK3
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxL3MYcTjfshb8eeK3
📓 Docs Drift Check7 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 801961354ea0bbb9a31225002eae1829c3a7e9f5 && git checkout 801961354ea0bbb9a31225002eae1829c3a7e9f5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c48cd2c103a9dfa99313a67a3ec622291d793900 ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2 && git checkout -B drift-repro c48cd2c103a9dfa99313a67a3ec622291d793900 && git merge --no-ff ea4b11b5e0b294a4d7adbc3ce2644a94a99a80c2
node scripts/docs-audit/affected-docs.mjs --json c48cd2c103a9dfa99313a67a3ec622291d793900 |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: At |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #28018 body and its three comments (claim 6526161926, file-surface amendment 6526184775 with the seat's option A, os-dev-report 6527607823); PR #28196 body, 12-file list and the diff against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Should-fix (none blocks the contract):
Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D 维护者速读
席位核对 · ACCEPT ·
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D
Requested by Tesla · project thread
Fixes #28018
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #28212 (skills/objectstack-ui/rules/list-views.md, to land with the renderer half #28211)
维护者速读
kanban.lanes(按顺序列出要画的泳道,取分组字段选项的存储值);os validate/os build/ 运行时发布闸门会拒收不是该字段选项的泳道值和重复泳道。lanes时触发。回滚即 revert 本 PR。控制台渲染器还没读这个键,在 ui 跟进卡落地前写了也仍是每个选项一列。packages/lint/src/validate-list-view-field-refs.ts(checkKanbanLanes/checkKanbanLaneRepeats两段)、packages/spec/src/ui/view.zod.ts(KanbanConfigSchema.lanes)、packages/lint/src/validate-list-view-kanban-lanes.test.ts。pnpm --filter @objectstack/lint exec vitest run src/validate-list-view-kanban-lanes.test.ts,预期 11 个测试全部通过(其中一条把inprogress判为不是选项,并提示Did you mean "in_progress"?)。What changes
Spec (
@objectstack/spec, minor).KanbanConfigSchema(a list view'skanbanblock) gains one optional key,lanes: string[]: the lanes the board draws, in this order, as stored option values ofgroupByField.Uncategorizedlane, as an off-lane record already does. To keep records off the board, filter the view (which is what the HotCRM task board already does).minItems), and an entry not shaped like a stored option value (each entry isSystemIdentifierSchema, the grammar ofSelectOptionSchema.value, so a label such asIn Progressis refused). Both project into the published JSON Schema exactly, so the key adds no row todropped-refinements.baseline.json.liveness/view.json: thekanbanrow noteslanesat planned strength (theviewMode-on-gantt precedent): at this treeui/packages/plugin-kanban/src/ObjectKanban.tsx#effectiveColumnsstill draws one lane per option and reads no lane list.Lint (
@objectstack/lint, minor). Spec cannot see the object, so the object-aware half lives wheregroupByFieldis already judged against the object,validate-list-view-field-refs.ts(the reference-integrity suiteos validate,os buildand the runtime publish gate run):list-view-kanban-lane-unknown(error): a lane that is not an inline option of the group field, atkanban.lanes[i], with the nearest option as "Did you mean". A misspelled lane draws a lane no card can land in while every card with the option it meant falls intoUncategorized.list-view-kanban-lane-repeated(error): a value listed twice, at the second entry. Needs no field map, so it runs before every skip.os explain.Alternative not taken: a
hideEmptyLanes: boolean. It hides a lane whenever its data happens to be empty, so lanes appear and disappear as records move and an empty lane stops being a drop target; and it cannot order lanes. The ordered list is stable, says which lanes and in what order, and closes the measured case by itself.Tests
packages/spec/src/ui/kanban-config-lanes.test.ts: member on the bare block and inside a list view (with an unknown-key control), order kept, absence writes nothing, empty list refused (too_small), label refused at its own index (invalid_format).packages/lint/src/validate-list-view-kanban-lanes.test.ts: the HotCRM board shape is clean; a non-option lane is one error at its path with the nearest option; a label is a non-option; a repeat is one error at the second entry, with and without an object universe; picklist-bound and boolean group fields are not judged; an unknowngroupByFieldis reported once at that key and its lanes are not judged;validateandbuildfail; the runtime save door (reference-integrity suite) reports both ids; bothos explainentries exist and every verdict and fix is one line of at most 200 characters.Acceptance notes
superRefineuniqueness check onlaneswould add a new dropped site to 16 entries of the shrink-onlydropped-refinements.baseline.json(measured: the spec build refused it), and growing that ledger is a decision, not a side effect. Keeping the duplicate verdict beside the membership verdict leaves the key fully projectable.picklist(its option set is the list plus everypicklistExtensionsentry, which a stack snapshot need not carry whole), and a group field with no inline options (boolean, lookup: their lanes are values). Both are stated in the module docblock and theos explainentry.packages/lint/src/index.tsgained the two id exports:rule-explanations.test.tsrequires every explained id to be a barrel-exported constant.lanesis the follow-up card Kanban board draws only the declared lanes, in their order: ObjectKanban reads kanban.lanes #28211. Noui/**edits here.Verification
All readings at head
ea4b11b5(branch merged withorigin/main412908cf), objectstack-ai/objectstack.Builds, package tests and typechecks
env -u OS_SKIP_DTS pnpm --filter @objectstack/spec buildthencheck:generated: exit 0, all artifacts current (reference docs regenerated in this PR; authorable surfaceui/KanbanConfig:lanesadded).turbo run buildover every non-ui, non-docs package: 77/77 tasks.pnpm --filter @objectstack/lint test: 179 files, 7650 passed, 5 skipped.pnpm --filter @objectstack/spec exec vitest run src/ui src/system/metadata-form-zod-reconciliation.test.ts src/type-alias-convention.pin.test.ts: 167 files, 5950 passed.pnpm --filter @objectstack/lint typecheckandpnpm --filter @objectstack/spec typecheck(tsc plus test-typecheck legs): exit 0.validate-list-view-kanban-lanes.test.ts11/11, speckanban-config-lanes.test.ts5/5.Ablation (lint rule, both verdicts)
Through
scripts/ablation-replace.mjs(anchor hit x1, restored to the HEAD blob34aa7c35,git diff HEADempty after each leg):return;:validate-list-view-kanban-lanes.test.ts3 failed / 8 passed.Derived gates (dispatch-gates --commands, 104 families)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 104 commands for the 12 changed paths; every one ran with stdin closed and exited 0. Reconciliation:dispatch-gates --ran: 104 derived, 104 run, 0 NOT-MEASURED, 0 UNRUN. Local-onlycheck-dev-prereqs.mjs: exit 1, theui/packages are unbuilt here (excluded from the local build; this diff touches noui/**). The full farm is CI's.Generated by Claude Code