Repository navigation
finding(types): ObjectKanbanSchema.groupBy is REQUIRED on both objectui faces while the protocol declares it optional — and that requiredness is what makes the protocol's bare-string columns arm unreachable #8990
Description
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanebugSomething isn't workingSomething isn't working
on Sep 10, 2026 Claim: session
session_01Jmxdo7bmeqCQHLSfmLVX9w(PM seatdomain:spec, dispatchingos-dev) · branchclaude/issue-8990-kanban-groupby-optional
Clause-②: yesThis widens objectui's accept set (
groupByrequired → optional), soyesis the honest declaration even though the widening is toward the protocol. ⛔ Provisional; your diff decides.Why this is decided, and by what
Maintainer principle now in force:
我们的项目以 objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议。
(source, and a route it overturned today: objectui#8934
5617613810.)The protocol declares
groupByoptional onobject-kanban; objectui requires it on both faces. ⇒ objectui is narrower than the protocol, which is the one direction the principle names as forbidden. The direction is settled; ⛔ the shape and the blast radius are yours to measure.⭐ The consequence that surfaced this card, and the reason it is not cosmetic: the renderer's bare-string
columnsbranch is gated on!schema.groupBy. WithgroupByrequired, the protocol's second element arm can never fire on a schema-valid objectui board. PR #8989 (landed 16:50Z) admits that arm anyway — refusing it would have been a second narrowing — and records it as unreachable. MakinggroupByoptional is what makes that arm reachable.Why it is dispatchable now
The file hold cleared when PR #8989 landed. Re-measured via
/pulls/N/fileson every openclaude/*PR, ⛔ not predicted: the only other open touch onpackages/types/src/objectql.tsis PR #8941's +1/-1 comment at:484insideBulkActionDef, andobjectql.tsis not inSINGLE_CLAIM_PATHS(one entry,.objectui-sha) ⇒ ordinary concurrency.⚠️ objectui#8992 is dispatched in the same batch and editsUserActionsSchemain the same two files. Expect a rebase; your region isObjectKanbanSchema.groupBy, theirs isUserActionsSchema/ the parity exemption / a docblock. ⛔ Do not touch theirs.⛔ What you owe
- Measure what actually breaks when
groupBygoes optional — this is a widening, so the risk is not a compile break, it is a runtime path that never ran before. Which code readsschema.groupByassuming it is present? What does the board do with nogroupByand nocolumns? What does it do with nogroupByand a bare-stringcolumns, the arm this unblocks? ⛔ "type-check is green" is not the answer to a widening. - A behaviour pin with a firing control for the newly reachable arm.
⚠️ If you find the arm is still unreachable for some second reason, that is a finding — report it, ⛔ do not paper over it. ⚠️ objectui#8993 is live and adjacent:bucketCardsIntoColumnsdouble-buckets any non-string lane id. PR types(plugin-kanban): declareobject-kanban.columnswith the protocol's element union #8989 closed the authored path by declaringid: z.string(). But the lanes the renderer materialises itself takeid: opt.valuestraight from picklist options, which no schema guards. If makinggroupByoptional changes which of those paths runs, say so. ⛔ Do not fix bug(plugin-kanban): bucketCardsIntoColumns double-buckets every record when a lane id is not a string — knownIds holds raw ids while the group keys are always strings #8993 here — it is a renderer behaviour change with its own card.- If you conclude the protocol is wrong to make
groupByoptional, that is an objectstack spec card first and this card blocks on it — ⛔ never a local divergence defended afterwards.
⚠️ Zone-2: the anchors and claims above come from another dev's measurements and from a protocol sweep; re-derive them on your own base. ⭐ On this very card family, a dev's own probe refuted the review's mechanism and its own earlier claim —String(col.id)was true at the i18n lookup and false at the site that decides lane membership. That is the standard.Standing rules
⛔ Never edit a shared primary checkout (
/home/user/objectuishallow + stale;/home/user/objectstackread-only). Clone fresh; report both HEADs and clean state at start and finish.
⛔ Nevergit stash; ⛔ never kill by name; ⛔ never mutatenode_modules; ⛔ never editcontent/docs/releases/; ⛔ never write the assignee or post a secondClaim:.
⭐ An ablation touchingpackages/typesneeds a rebuild leg and dist markers read both ways —tsconfig.test.jsonsets an emptypaths, so a skipped rebuild measures the old artifact and reads as "the mutation changed nothing".
Capture everyrcto a file before any pipe; read lint logs for the literal worderror, excluding source context inside warnings.
⭐ Wait for CI to reach TERMINAL state before you report.
⛔ PR staysdraft. GitHub content in English.
Generated by Claude Code
- Measure what actually breaks when
os-dev-report
{ "issue": 8990, "status": "done", "branch": "claude/issue-8990-kanban-groupby-optional", "pr": "https://github.com/objectstack-ai/objectui/pull/9021", "premise_still_valid": true, "summary": "ObjectKanbanSchema.groupBy is now OPTIONAL on both published faces (packages/types/src/objectql.ts and packages/types/src/zod/objectql.zod.ts), matching @objectstack/spec, which declares groupBy: z.string().optional(). Re-derived on my own base (2596b1b85): both faces did require it, so objectui refused a document the protocol accepts. I concluded the PROTOCOL IS RIGHT and filed no upstream spec card, on this repo's own corpus rather than deference: content/docs/utilities/data-objectstack.mdx documents a lane-less { type, dataSource } board, and plugin-list/src/ListView.tsx GENERATES the node as groupBy: laneField where laneField ends in an explicit || undefined. Both were recorded at objectui#7780 as readings excluded from objectui#7322's requiredness measurement; they are the evidence refuting it. This is a widening: an authored groupBy is still declared and enforced, and every flipped pin gained a CONTROL so optional cannot decay into undeclared.", "tests": "All on final head fe330a776, fresh clone, heavy runs through the shared verify lock. GREEN: @object-ui/types test 168 files/3341 passed; @object-ui/types type-check exit 0, 0 errors (covers tsconfig.test.json, where the type-level pins live); @object-ui/plugin-kanban test 48 files/276 passed; block-config.test.ts 43 passed; type-check plugin-kanban + plugin-list + plugin-view + app-shell all exit 0 / 0 errors. Gates (own verdict lines): check:control-bytes 'OK (scanned 7212 tracked text file(s))'; check:new-line-citations 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'; check:spec-symbols 'nothing cites a key its spec symbol does not declare'; check:designer-field-key-parity 'designer-field-key-parity: OK'; check:comment-mask-corpus within the pre-existing objectui#7882 residue (unchanged); check:changeset-presence '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check:governed-queue-guard --test 'NOT GOVERNED - none of 6 paths matched'. NOT MEASURED then remeasured: consumer type-checks first reported 36/48/43 errors, ALL TS2307 'Cannot find module @object-ui/components' = unbuilt dependency closure, not red; after pnpm --filter 'PKGNAME^...' build all four are 0. ABLATION (committed first, trap on EXIT INT TERM, restore from HEAD): both faces reverted to REQUIRED, source-only, no rebuild. On-disk proof both directions - TS removed-text count=0/injected count=1, ZOD removed-text count=0/injected count=1. ABLATED_EXIT=1, Test Files 3 failed, Tests 8 failed | 47 passed. Restore leg: git diff HEAD empty, git status clean, markers back at 1/1, then 55/55 green. DIST MARKER READ BOTH WAYS as required: dist/objectql.d.ts held groupBy?:string=1 / groupBy:string=0 BEFORE and STILL 1/0 DURING the mutation - unchanged, so the red came from SOURCE via vitest's @object-ui/types alias, not from a stale or rebuilt artifact (tsconfig.test.json's empty paths is the typecheck path, not this one). CI TERMINAL on fe330a776: 34/34 completed, 31 success + 3 skipped, ZERO failures - all four Test shards waited out to completion (shard 2/4 finished last, ~7 min after the first green reading).", "mcp_calls": "0 - REST probe returned 200 in-container, so every read and write went through repo-scoped REST (issue, comments, labels, PR create, check-runs); zero MCP GitHub calls for the whole run.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: KanbanImpl's isBoardEmpty = totalCardCount === 0 && boardColumns.length > 1, so a ZERO-lane or one-lane board never paints the objectui#8827 'No cards' live region. Pre-existing and independent of this card (the predicate does not read groupBy) and reachable today by a grouped board whose result is empty. Successor: the objectui#8827 empty-state family. It forced a rig change in my pin - one leg settles on the board region plus a paired control instead of the empty state, and says so at its site.", "noted, not filed: the six cross-file line citations in the groupBy docblock (:601 :613 :625 :640 :747 :865 into ObjectKanban.tsx) had ALL rotted - the same reads now sit ~370 lines lower. The dispatch's own anchors carried the same stale numbers, which Zone-2 re-derivation caught. I replaced my census with SYMBOL anchors rather than fresh line numbers. No card: check:new-cross-file-line-citations already owns this class and is deliberately report-only; note it scores this diff at '0 new citation(s)' because the bare :NNN form is not one it parses, which is exactly the form that rotted.", "noted, not filed: ListView's availableViews gate decides whether to OFFER the kanban view type from the view-level groupByField/groupField bindings, not from this node schema, so the widening does not make kanban newly offered anywhere. Verified, no change needed." ] }
Generated by Claude Code
Seat dispositions — delivered, and it corrected two things this seat got wrong
PM seat (
domain:spec@ objectui, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w), 2026-09-10. PR #9021, headfe330a7768b009bd0e568322bf7e4cea11250e22. CI terminal: 34/34, 31 success + 3 skipped, zero failures — all four shards waited out, shard 2/4 finishing ~7 min after the first green reading.⛔ Correction 1 — my framing of what this card unblocks was too strong
I wrote that making
groupByoptional "is what makes the bare-stringcolumnsarm reachable." Measured, that is not right, and the dev's ablation shows why: re-requiringgroupByreddens only the contract legs of its pin; every render leg stays green.Mechanism:
SchemaRendererruns the structuralvalidateSchema, never the zod mirror. ⇒ the renderer's string branch was always live for any document that reaches it unvalidated —ListView's generated node, or any host not runningos check. What requiredness prevented was a schema-valid document getting there.⭐ Now written into the pin file so nobody mistakes the render legs for guards on the declaration. That distinction is the whole difference between "the declaration gates this" and "the declaration describes this."
⛔ Correction 2 — my label instruction was written before the PR existed
I said
needs:contract-reviewwas already on both carriers and ⛔ not to touch it. The PR did not exist when I wrote that, and it came up carrying only auto-labeler labels. The claim declaresClause-②: yes, so the standing rule owes the label on the PR side. The dev added it via the additive endpoint and read back comparatively — nothing stripped.That resolution is correct and I am not reversing it. ⭐ It surfaced the conflict and said "reverse it if you meant the issue label alone" rather than either silently obeying a stale instruction or silently ignoring it. That is the right handling of an order that has gone out of date.
⭐ It decided the protocol question on this repo's own corpus, not by deference
Asked to judge whether the protocol is right to make
groupByoptional, it did not answer "the principle says the protocol wins." It found the evidence:content/docs/utilities/data-objectstack.mdxdocuments a lane-less{ type, dataSource }board;plugin-list/src/ListView.tsxgenerates the node asgroupBy: laneField, wherelaneFieldends in an explicit|| undefined.
⇒ objectui was refusing a document objectui itself produces. And both readings were recorded at objectui#7780 as having been excluded from objectui#7322's requiredness measurement — they are the evidence that refutes it, sitting in the tree the whole time. No upstream spec card is owed.
⭐ objectui#8993 is strictly NARROWED by this change, asserted both ways
I asked whether widening
groupBychanges which of #8993's two paths runs. Measured: the double-bucketing sits afterbucketCardsIntoColumns's!groupByearly return, and the unguardedid: opt.valuelanes are built underif (schema.groupBy && …)— a branch a lane-less board cannot enter. ⇒ this card cannot widen #8993's blast radius; nothing about it was touched.⭐ Zone-2 refuted the dispatch's anchors, and refused to mint new ones
All six cross-file line citations in the
groupBydocblock (:601:613:625:640:747:865intoObjectKanban.tsx) had rotted — the same reads now sit ~370 lines lower, and my dispatch carried the same stale numbers. It replaced the census with symbol anchors rather than fresh line numbers that would rot again.⚠️ And it named a gate blind spot in passing:check:new-cross-file-line-citationsscores this diff at "0 new citation(s)" because the bare:NNNform is not one it parses — which is exactly the form that rotted. The dev declined to file, on the grounds that the gate already owns the class and is deliberately report-only. ⭐ I am carrying it forward rather than closing it: a gate that is report-only about a class it cannot parse is not report-only about that class, it is silent on it. Whether that is worth a card is a judgement for the review round; I am not deciding it alone.Ablation precision worth keeping
The dist marker was read both ways and came back unchanged (
groupBy?:string= 1,groupBy:string= 0, before and during). ⇒ the red came from source, via vitest's@object-ui/typesalias — ⛔ not from a stale or rebuilt artifact. The dev correctly distinguished which path was in play:tsconfig.test.json's emptypathsis the type-check path, not the vitest one. That is more precise than the blanket "always add a rebuild leg" I put in the dispatch.Next
needs:contract-reviewstays on both carriers; the in-seat clause-② review is dispatched against this head. ⛔ Nothing lands until a 同形 PASS record sits on it.
Generated by Claude Code
os-dev-report
{ "issue": 8990, "status": "done", "branch": "claude/issue-8990-kanban-groupby-optional", "pr": "https://github.com/objectstack-ai/objectui/pull/9021", "premise_still_valid": true, "summary": "REWORK ROUND after contract review returned VERDICT: REWORK on one blocking finding (R1, documentation). R1 fixed: four hand-written doc sites still asserted the pre-change contract and TWO of them named this very card as the pending change, so the PR closed the card while leaving the docs saying it was still open (AGENTS.md #2 is binding; no CI gate catches this class). schema-reference.md property table and columns callout, plugin-kanban.mdx callout, and plugin-kanban/README.md now all state optional AND the measured lane-less behaviour (bare-string lanes drawn and titled by RAW values vs the picklist LABELS a grouped board shows, zero cards, moves inert); both 'tracked as objectui#8990' sentences deleted. I also corrected TWO FURTHER README sites the review did not name (:48, :109) making the same requiredness claim, and made explicit in schema-reference.md that the record-source rule is separate from the lane key and unaffected. Non-blocking items also done: the two unqualified 'what made the arm live' sentences now carry the schema-valid qualifier; the #8993 wording is corrected from 'can only NARROW' to UNCHANGED / not widened; the six rotted line addresses are no longer restated even as history. The underlying contract change is unchanged from the reviewed head.", "tests": "All on final head fa93c7fc2, fresh clone, heavy runs through the shared verify lock. GREEN: @object-ui/types test 168 files/3341 passed; @object-ui/types type-check exit 0 / 0 errors; @object-ui/plugin-kanban test 48 files/276 passed; block-config.test.ts 43 passed. DOC GATES (this round's real risk surface): check:doc-snippets '638 of 638 block(s) judged, 0 failed' + 'Every covered documentation snippet compiles against the built types'; check:doc-types 'Every documented component type is registered'; check:doc-fences OK across 227 documents. Also check:control-bytes 'OK (scanned 7212 tracked text file(s))'; check:new-line-citations '0 new citation(s), enforcement report-only -> exit 0'; check:changeset-presence '6 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'; check:governed-queue-guard --test 'NOT GOVERNED'. NOT MEASURED, then measured: check:doc-snippets first returned 'PRECONDITION NOT MET (exit 2) - The snippet program was NOT run: the packages it resolves against are not built' - read as not-measured, not red, and re-run only after the scoped 35-package build it prescribes. ABLATION NOW WITH THE TSC LEG the review found missing from round 1: baseline head tsc exit 0 / 0 errors; on-disk proof TS removed=0 injected=1, ZOD removed=0 injected=1; ablated vitest exit 1 = Test Files 4 failed, Tests 9 failed | 89 passed (98); ablated tsc exit 2 = 4 errors, exactly the four the review measured (7322:147 TS2344 _GroupByIsString, 7322:153 TS2344 _GroupByIsOptional, 7322:181 TS2741 lanelessLiteral, 7780:164 TS2741 LANELESS_BOARD_NOW_ACCEPTED). The vitest count matches the review's independent run exactly (its 8/47 over three files plus the block-config failure my set adds). Restore leg: git diff HEAD empty for both faces. CI TERMINAL on fa93c7fc2: 35 check runs, all completed, 32 success + 3 skipped, ZERO failures - all four Test shards waited out (shards 2/4 and 3/4 finished last).", "mcp_calls": "0 for this round as well (repo-scoped REST throughout: review comment read, PR PATCH, check-runs, report comment). Cumulative for the card: 0.", "open_questions": [ { "question": "The review asks that the KanbanImpl empty-state gap be written onto objectui#8827 or filed, not only 'noted'. isBoardEmpty = totalCardCount === 0 && boardColumns.length > 1, so a zero- or one-lane board never paints the #8827 'No cards' live region; it is pre-existing and groupBy-blind, but this widening does let a schema-valid { type, objectName } board reach a silent blank that core validateSchema has no rule about. Your rework brief did not name it and you reserved the gate card for yourself, so I did not act unilaterally.", "options": [ "A - I post a measurement comment on objectui#8827 recording the predicate, the newly reachable document class, and the rig consequence it forced in my pin", "B - I file a separate card (needs the dedupe search first) and link it from #8827", "C - You take it, as you took the citation-gate card" ], "recommendation": "A, because #8827 already owns this exact empty-state predicate and the finding is one paragraph of measurement rather than a work item of its own; B duplicates an owner, and C costs you a round-trip for something I have already measured." } ], "out_of_scope_findings": [ "PROCESS FAILURE, mine, recorded rather than quietly fixed: I ran this round's ablation on an UNCOMMITTED tree, so its restore-from-HEAD leg reverted my own in-progress edits to objectql.ts along with the mutation. git status caught it; the edits were re-applied and COMMITTED before re-verification, and the measurements were unaffected because they tested the committed faces. The rule I broke is my own: commit before ablating, because the restore leg points at HEAD.", "boundary respected, with one clarification: this round's commit does not touch packages/types/src/zod/objectql.zod.ts, which PR #9017 holds. The PR AS A WHOLE necessarily does - that is the mirror half of the contract change, from round 1 - so the hold cannot be fully avoided by this card. Probed rather than assumed: my hunks are at :1216 and :1268, #9017's at :590, and git merge-tree reports a clean textual merge with no conflict.", "noted, not filed: I left one imprecise sentence in objectql.zod.ts untouched - it cites the mdx fragment among documents 'this validator refused', which is historically true but carries the same implication the review had me downgrade elsewhere. Fixing it means editing the file PR #9017 holds, the review did not name it, and its 'Owed before re-review' list does not include it. Flagging rather than touching; one line to fix once #9017 lands.", "noted, not filed: the citation-gate finding (scanner parses a continuation :NNN only within 2 lines of a numbered hit, so the six rotted addresses were invisible - silence about a class, not report-only about it) is measured and is yours to file, per your brief. Not changed here." ] }
Generated by Claude Code
Release: MERGED — objectui#9021
ObjectKanbanSchema.groupByis optional on both published faces onmain.Landing commit
a4b723fab99b5e74917e03598382d25d15f487da· merged 2026-09-10T20:22:19Z by the queue · squash, single parent152f0a700· 10 files, +738 −90.Two readings, ⛔ never a single API field
- Timeline —
added_to_merge_queue20:04:25Z →removed_from_merge_queue20:22:18Z →merged20:22:19Z →closed20:22:19Z. origin/mainby content —
git merge-base --is-ancestor a4b723fab99b5e74917e03598382d25d15f487da origin/main rc=0 git merge-base --is-ancestor fa93c7fc259025528afee61e5fe9ead3926ac3dc origin/main rc=1 <- the control, and it FIRES⚠️ The reversed probe is degenerate here — the landing commit isorigin/main's tip, so--is-ancestor origin/main <landing>would answer rc 0 too and prove nothing. The control that actually fires is the pre-queue PR headfa93c7fc2…: it exists as a commit (cat-file -t→commit) and is not an ancestor ofmain, because the queue lands a different commit. That is the charter's own trap, measured rather than recited.Behaviour verification — ⛔ not commit presence
reading on main@a4b723fabvalue firing control packages/types/src/objectql.ts:3033groupBy?: string;type: 'object-kanban';in the same interface has no?— the optionality detector distinguishespackages/types/src/zod/objectql.zod.ts:1286groupBy: z.string().optional().describe(…)objectName: z.string()(no.optional()) at:229/:288/:383new pin plugin-kanban/src/__tests__/laneLessBoard-8990.test.tsxpresent, 16063 B a sibling path this PR did not add → No such file"tracked as objectui#8990"undercontent/+packages/0 "objectui#8990"at all → 36, so the query is live, not dead⚠️ One residue this landing creates, measured — it is NOT undercontent/orpackages/.changeset/8913-object-kanban-columns-declared.md:69still reads "requiredness is tracked as objectui#8990" — now false, and it is a pending changeset, so it ships that sentence intopackages/types/CHANGELOG.mdat the next release. My first sweep missed it because it coveredcontent/andpackages/only;.changeset/is neither. Folded into card objectui#9042, which already carries a sibling changeset correction and must land before@object-ui/typesnext publishes.Field reading
Commit date 20:04:24Z →
merged_at20:22:19Z = 17m55s. Against the 17m31s–17m44s band from nine earlier landings and one 37m43s outlier: ⛔ a band is neither a ceiling nor a floor, and this point sits just outside it.Review history
Round 1
REWORK(documentation: four doc sites still asserting the pre-change contract, two of them naming this very card as pending) → repair, which also fixed two sites the review had not named (plugin-kanban/README.md:48,:109) and added the missingtscablation leg, reproducing the reviewer's four errors exactly → round 2VERDICT: PASS, record5624643821, ceiling tier transcript-verified 95/95, independence NOT self-review.Card auto-closed by the PR's keyword (
state_reason: completed);pm:dispatchedstripped in this same act.
Generated by Claude Code
- Timeline —
- added a commit that references this issue
on Oct 1, 2026
Filed by the
os-devseat while executing objectui#8913 (PR #8989), sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w. Deliberately left alone there: objectui#8913's scope is one declaration, and this is a different key.Notation. Generic parameters are written as UPPERCASE WORDS, never as literal tag-shaped fragments, because GitHub silently eats those from a stored body.
The reading
objectstack-ai/objectstackateabdd66f45f402eba0f8404a8a9de4a501fc83a6,packages/spec/src/ui/component.zod.ts, insideObjectKanbanPropsSchema:objectstack-ai/objectuiatefead6c6054371d8c487e98395d44a7743ab7bbc, both published faces ofObjectKanbanSchema:packages/types/src/objectql.ts—groupBy: string;(REQUIRED)packages/types/src/zod/objectql.zod.ts—groupBy: z.string()(REQUIRED, no.optional())So objectui refuses a document the protocol accepts: an
object-kanbannode with nogroupBy.Why this is being raised now rather than left alone
The requirement is deliberate and documented (objectui#7322, restated at objectui#7780: "a board is a grouping of records by one field"). What changed is the principle now in force, relayed on objectui#8934 (recorded verbatim, untranslated):
Under it, "objectui is narrower than the protocol on a published key" is the direction that needs a ruling — either objectui relaxes, or the protocol is corrected first. Today neither has happened; the divergence just sits there.
The consequence measured on objectui#8913, which is what surfaced it
The protocol declares
object-kanban.columnswith a two-arm element union — a{ id, title }lane, or a bare value string. objectui's renderer does honour the bare-string arm, but the branch is gated on the absence ofgroupBy:packages/plugin-kanban/src/ObjectKanban.tsx, theeffectiveColumnsmemo:⇒ Because
groupByis REQUIRED on this face, every document that passes objectui's own validator hasgroupByset, so the bare-string arm can never fire. The protocol's second arm is inert here — not by a decision aboutcolumns, but as a side effect of this key's requiredness. PR #8989 admits the arm anyway (refusing it would be a second narrowing), and records that it is unreachable rather than pretending otherwise.Renderer-side, the
groupByreads are guarded early-returns (if (!schema.groupBy)at two sites), so a lane-less board does not crash — it renders the empty-lane path.What a fix would have to weigh
optional, matching the protocol. Widens a published accept set — a ruling of its own. It would also make the bare-stringcolumnsarm reachable, which is the only way that arm becomes live.groupBy. An@objectstack/speccard; objectui then mirrors it and nothing here changes. The protocol's own docs example authorsgroupBy, so the corpus does not contradict this route.⛔ Not a recommendation from this seat: which of the three is right is a contract question, and objectui#8913's dispatch is explicit that a narrowing question belongs upstream rather than to the implementing seat.
Not this
kanban.groupByField/groupFieldalias, which is live and untouched (objectui#7322 is explicit that the retirement is node-local).groupFieldon the node, which is a tombstone and stays one.groupByoverriding a resolved lane inplugin-list; this card is about the node declaration's requiredness instead.Refs: objectui#8913 / PR #8989 · objectui#7322 · objectui#7780 · objectui#8934.
Filed by Claude Code for the
os-devseat on objectui#8913; the session reference issession_01Jmxdo7bmeqCQHLSfmLVX9w.Generated by Claude Code