Skip to content

feat(ui): kanban board draws only the declared lanes, in their order - #28291

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-b1o0dq
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-b1o0dq

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #28211
Refs #28018

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:看板视图现在按 kanban.lanes 只画作者列出的泳道、按作者的顺序;两条生成看板的路径(列表视图与 object-view 元素)都把它映射到看板已有的 columns 键上。
  • 为什么改:键已在 spec 里声明,但 Console 不读它,HotCRM 任务看板仍画一条永远为空的「已完成」泳道,作者也排不了泳道顺序。
  • 风险与回滚:没写 lanes 的看板完全不变;未列出取值的记录落进「未分类」泳道,不会被藏起来。回滚即撤销本 PR。
  • 先看:ui/packages/core/src/utils/kanban-declared-lanes.ts(映射规则),ui/packages/plugin-list/src/ListView.tsx 的 case 'kanban',ui/packages/plugin-view/src/ObjectView.tsx 的 case 'kanban'。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-kanban/src/ObjectKanban.declaredLanes-28211.test.tsx,预期 3 个用例全绿(只画列出的泳道、顺序对、未列出的进「未分类」)。
  • 需要你做的:无,席位落地。

What changes

  • @object-ui/core: new kanbanDeclaredLanes(lanes, options). It turns a list view's kanban.lanes into the object-kanban node's columns, in the author's order. Each lane is titled with its option's label, or with the value itself while the definition is still loading. A lane whose option has kind: 'lost' gets collapsed: true, keeping the Stage path and kanban read the select option's lifecycle kind: terminal options drawn apart from the steps, lost ones marked, instead of guessing from closed_won / closed_lost spelling #26612 behaviour. No lanes returns undefined.
  • @object-ui/plugin-list (ListView, case 'kanban') and @object-ui/plugin-view (ObjectView#generateViewSchema, case 'kanban'): both producers of the node read kanban.lanes by name and emit columns only when lanes are declared. generateViewSchema now also depends on objectSchema, so the titles follow the definition once it lands.
  • ObjectKanban.tsx is unchanged. Its explicit-columns branch already draws columns in order and localizes each title. A record whose value matches no lane already lands in Uncategorized, both when loaded rows are bucketed and when lanes page on their own: pagedLaneIdsKey reads declared columns first, and the uncolumned lane's query is "not in the listed ids".
  • packages/spec/liveness/view.json: only the kanban row changes. Its note moves lanes from planned to live and cites the readers by symbol.
  • Root changeset .changeset/28211-kanban-declared-lanes.md, with every package at patch.

Why the producers and not ObjectKanban: the node's face, ComponentPropsMap['object-kanban'], declares no lanes, and both producers forward only named block keys. Mapping onto the existing columns key needs no spec edit. The seat's claim amendment records this surface.

Tests

  • New ui/packages/core/src/utils/__tests__/kanban-declared-lanes-28211.test.ts (4 cases): order and labels, no lanes, author order over lifecycle order with lost collapsed, value title before options load.
  • New ui/packages/plugin-kanban/src/ObjectKanban.declaredLanes-28211.test.tsx (3 cases): the real board draws only the listed lanes in order, an unlisted value lands in Uncategorized, and a listed lost lane starts collapsed.
  • New ui/packages/plugin-list/src/__tests__/ListView.kanbanDeclaredLanes-28211.test.tsx and ui/packages/plugin-view/src/__tests__/ObjectView.kanbanDeclaredLanes-28211.test.tsx (2 cases each): the generated node carries the declared columns, and a control shows that no lanes means no columns.
  • Ablation: the columns spread was deleted with scripts/ablation-replace.mjs in each producer, and restore was proven by blob hash. Predicted and observed: the columns case of that producer's test goes red (1 failed | 1 passed) and its control stays green.

Acceptance notes

  • S2 (the checkKanbanLanes docblock line) does not ride. That module is packages/lint/src/validate-list-view-field-refs.ts, outside the liveness file. The KanbanConfigSchema.lanes TSDoc in packages/spec/src/ui/view.zod.ts still says it "Lands ahead of its reader", which is now false. Both are spec-source edits that the seat files as a domain:spec follow-up after merge.
  • In ListView.tsx, one hunk sits outside case 'kanban': the @object-ui/core import line gains kanbanDeclaredLanes. That matters for the serial overlap with seat 4's lint/types: 14,366 no-explicit-any warnings can never red CI — make it an error on packages/types/src, delete the vestigial (schema as any) reads, and type ComponentRendererProps with unknown #25918 in the same file. When this branch was cut, origin/main had no change to that file.
  • The renderer does not dedupe or filter lanes. Membership and repeats are judged at authoring (list-view-kanban-lane-unknown / -repeated in @objectstack/lint).
  • ui AGENTS.md ✨ Set up Copilot instructions #2 (docs-driven) is not covered: no README or content/docs/frontend/ page is touched, because both are outside the claimed surface. The spec key's own docs already describe the behaviour.

Verification

Head 8f4d9c61, base 6285203d.

Package suites and type-checks
  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/core/ packages/plugin-kanban/: 306 files, 4723 passed, 27 skipped.
  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-view/: 78 files, 716 passed.
  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-list/ in two shards: 85 files / 794 passed, then 89 files / 908 passed.
  • pnpm run type-check in ui/packages/core, plugin-kanban, plugin-list and plugin-view: all exit 0, after a real dts build of their closure.
  • eslint --no-inline-config on the changed TS files: exit 0 (0 errors).
Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 67 commands. Every one was run with stdin closed. Reconciliation with --ran: 67 accounted for, 66 run and 1 NOT MEASURED.

  • pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: needs a full workspace dist). NOT MEASURED locally; CI runs it.
  • node scripts/check-plugin-teardown-shape.mjs --self-test first exited 3 because the shallow clone lacked its pinned fixture commit. After git fetch origin 621a4876… --depth=1 it exited 0 (48 cases).
  • All other 65 derived commands exited 0, including node scripts/check-sdui-manifest.mjs, pnpm --filter @objectstack/spec run check:liveness, pnpm check:doc-authoring, pnpm check:test-source-alias and pnpm --dir ui exec vitest run scripts/.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L9qibymteTr9Zsy1aSkJPt


Generated by Claude Code

Both object-kanban producers (ListView and ObjectView) map kanban.lanes
onto the node's columns through one @object-ui/core helper,
kanbanDeclaredLanes: the author's order, option labels as titles, a lost
lane collapsed. An unlisted value still lands in Uncategorized.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibymteTr9Zsy1aSkJPt
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/liveness/view.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/view.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8f36f6b0604123f328841138448b375ebc2af946 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 8f4d9c61b98b1a7a09cd139bd23b6d57759534af · 2026-10-31T03:44Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8f4d9c61b98b1a7a09cd139bd23b6d57759534af
Local-runs: none
Scope: ui/packages/core/src/utils/kanban-declared-lanes.ts, ui/packages/core/src/index.ts, ui/packages/plugin-list/src/ListView.tsx (the case 'kanban' branch and the import line), ui/packages/plugin-view/src/ObjectView.tsx (generateViewSchema's case 'kanban' and its memo deps), packages/spec/liveness/view.json (the kanban row), the four new tests, .changeset/28211-kanban-declared-lanes.md + ui/packages/plugin-kanban/src/ObjectKanban.tsx (effectiveColumns, pagedLaneIdsKey, the lane queries), ui/packages/plugin-kanban/src/KanbanLanePaging.ts (uncolumnedQueryFilter), ui/packages/plugin-kanban/src/KanbanImpl.tsx, packages/spec/src/ui/view.zod.ts (KanbanConfigSchema.lanes), ui/packages/types (the object-kanban columns shape)

Inputs: card #28211 (body and all five comments: triage 6530215469 with its one-file cross-domain exception, the seat's claim 6537960788, amendment 6537973671 which moves the surface from ObjectKanban to the two producers plus one core helper, the dev report 6538446315, amendment 2 6538460840 which flips Clause-② to widening and routes the PR here), PR #28291 (body, the one docs-drift comment, the file list of 10 files, the diff of 644 lines against main read whole, the three commits from merge-base 8f36f6b), the 69 check-runs on this head (read 2026-10-31T04:01Z: all 69 runs completed, none failed, none cancelled, none in progress; success: Build & E2E UI, Build Core, Build UI Deps, Check Changeset, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Spec property liveness, Test Core, Test Core (all 8), Test UI, Test UI (all 16), Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter; skipped: 18.x upgrade harness (opt-in), Auto Label, Build Docs, Check PR Size, Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), UI Published Artifacts), and the tree at this head for every claim the body makes about the board. The head is mergeable and clean against today's main (merge-tree clean; ListView.tsx, ObjectView.tsx and ObjectKanban.tsx are untouched on main since the merge-base, so the #25918 overlap the body names has not arrived).

① Derived judgments

  • The card's effect, by the amended mechanism - right. ObjectKanban never receives kanban.lanes: both producers forward named block keys only, and the object-kanban node declares columns (its lanes, { id, title, collapsed? }) but no lanes. The helper kanbanDeclaredLanes(lanes, options) maps the declared list onto columns in the author's order, titles each lane from its option's label (the value itself until the definition loads), marks a kind: 'lost' option collapsed: true, and returns undefined for no or empty lanes, so a view without the key builds the node it built before (pinned by both producers' CONTROL cases). ListView reads lanes off the block beside columns, titleField and summarizeField and spreads columns only when lanes exist; ObjectView#generateViewSchema does the same from kanbanCfg.lanes and adds objectSchema to its memo deps so titles follow the definition once it lands (ListView's memo already depends on objectDef).
  • The board honours explicit columns as given - right. effectiveColumns (ObjectKanban.tsx:1453) returns object-shaped schema.columns through localizeColumn only, so the author's order stands and a translated label still wins; the lifecycle ordering (lanesFromOptions, Stage path and kanban read the select option's lifecycle kind: terminal options drawn apart from the steps, lost ones marked, instead of guessing from closed_won / closed_lost spelling #26612) applies only to lanes derived from options, which is the branch declared lanes bypass, and the helper carries the lost-lane collapse across instead. Pinned on the real board (ObjectKanban.declaredLanes-28211.test.tsx rows a and c).
  • Nothing is hidden - right, on both data paths. When lanes page on their own, pagedLaneIdsKey (:717) reads object-shaped schema.columns ids first and the uncolumned query is uncolumnedQueryFilter (KanbanLanePaging.ts:106): base AND (laneField nin laneIds OR is_null), so every unlisted value is fetched into Uncategorized. When loaded rows are bucketed client-side, a value matching no column lands in the Uncategorized lane as before; pinned on the real board with a record whose value is unlisted (row b). Membership and repeats are judged at authoring (list-view-kanban-lane-unknown / -repeated, feat(spec,lint): a kanban board chooses which lanes it draws, and in what order (kanban.lanes) #28196), so the renderer rightly takes the list as declared.
  • Accept-set: nothing an author writes moves - right. No spec schema changes; packages/spec/liveness/view.json changes only the kanban row's note, moving lanes from planned to live and citing both producers, the helper and the board's effectiveColumns by path and symbol, which is what the card asked and what the triage exception allows. check:liveness is the gate for that note's shape.
  • Public surface - @object-ui/core gains kanbanDeclaredLanes and the types KanbanLaneOption, KanbanDeclaredLane on the barrel; nothing else. The seat's amendment 2 records the widening.
  • Check-runs as gate verdicts - read as above; every run on this head completed and none is red, cancelled or in progress at the read.

② Semver level

  • @object-ui/core, @object-ui/plugin-list, @object-ui/plugin-view, @objectstack/spec all patch; Clause-②: yes (widening); Metadata-protocol: none - accepted. The core export is additive and the two producers change behaviour only when a key that was declared-but-inert is present; the spec entry covers a liveness note, not a published type or schema, so patch is the most it could be. Metadata-protocol: none is right: no key, value or default an author writes moves.

③ Boundary flags

④ Design judgment

  • Nothing blocking, and no should-fix on the diff. Tried: a lane value the options do not describe (titled with the value, re-titled when the definition loads, because both producers' memos depend on the definition); numeric option values (String() on both sides of the map, and the board compares lane ids as strings); explicit lanes with lane paging on (declared ids drive the per-lane queries and the uncolumned query is the complement); a listed lost lane (collapsed via the node's own collapsed, pinned); an empty lanes array (treated as undeclared, pinned). The mapping lives in one function both producers call, which is the right place for a rule two routes must agree on.

Verdict reasoning. The PR delivers the card's effect through the mechanism the seat amended before any edit, with ObjectKanban unchanged and the board's existing columns contract doing the work; the author's order wins, nothing is hidden on either data path, a view without lanes is byte-for-byte the same node, and each claim is pinned on the real board or the real producer with an ablation shown red. The liveness note is the one cross-domain edit triage allowed; semver is patch everywhere with the widening declared. The stale spec TSDoc is a named follow-up outside this card's surface.

Implemented-by: claude/project-thread-b1o0dq
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN

席位核对

ACCEPT · seat domain:ui#1 · head 8f4d9c61 · 2026-10-31T04:02Z

席位意见:看板视图声明了 kanban.lanes 后,现在只画列出的泳道,并按作者给的顺序排列。HotCRM 的任务看板不再多出一个永远为空的「已完成」列。值不在列表里的记录落到「未分类」,不会被隐藏;没有声明 lanes 的视图和以前完全一样。看板组件本身没改,两个产出看板节点的地方共用 @object-ui/core 新增的一个函数,把声明的泳道交给看板已有的 columns。已按扩面申报 Clause-②: yes (widening)。

  1. Shape: Fixes #28211, Refs #28018, draft, base main, 10 files, +510/−7. Adds ui/packages/core/src/utils/kanban-declared-lanes.ts plus its export. Touches the case 'kanban' branches of ListView.tsx and ObjectView.tsx, with the objectSchema memo dep. Flips the packages/spec/liveness/view.json kanban row, lanes planned → live, which is the triage's one-file exception. Adds four test files and a root changeset. This matches claim amendment 1 (6537973671).
  2. Seat read: ObjectKanban.tsx is unchanged. columns is spread only when lanes are declared, and both producers' CONTROL cases pin the undeclared node. The one hunk outside case 'kanban' is the @object-ui/core import line in ListView.tsx. Seat 4's lint/types: 14,366 no-explicit-any warnings can never red CI — make it an error on packages/types/src, delete the vestigial (schema as any) reads, and type ComponentRendererProps with unknown #25918 hasn't landed, and merge-tree is clean.
  3. Contract review: PASS at this head, record 6538743783 (CONTRACT_REVIEW_TIER), by the contract-review lane, not the drafter. Nothing in it blocks, and it has no should-fix on the diff. Its boundary flag is the stale KanbanConfigSchema.lanes TSDoc plus rider S2. The seat files both as one domain:spec follow-up when this lands.
  4. Gates / check-runs: on 8f4d9c61, 69 runs: all completed, 0 failure, including Spec property liveness and check:dual-build-cjs-loads via Build. mergeable_state clean. Dev's local run: 66 of 67 derived gates green, and the one not measured locally is covered by CI.
  5. Landing: check-governed-merges --pr 28291 reports NOT governed, 517 changed lines ≤ 3000. The seat readies the PR and arms auto-merge, on the maintainer's 「按 pm-dispatch 审过的pr你可以自主落地」 (seat thread, 2026-10-30T14:19Z).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kanban board draws only the declared lanes, in their order: ObjectKanban reads kanban.lanes

1 participant