Repository navigation
fix(app-shell): stop emitting the third kanban lane spelling groupBy - #8356
Merged
Merged
Conversation
`ObjectView.kanbanViewOptions` wrote `{ groupBy: lane, groupByField: lane }`
into the view-level kanban config it hands to `list-view`. Measured against
`@objectstack/spec` 17.3.0, the strict `KanbanConfigSchema` declares
`groupByField` / `summarizeField` / `columns` and refuses `groupBy` BY NAME,
with a control that fires on the same call and a control that accepts — so the
refusal carries information. Upstream knows the other legacy spelling by name
("Did you mean `groupField` to `groupByField`?") and knows nothing about
`groupBy`: this repo was producing a third dialect for one concept.
A producer census over every tracked file — a bare `groupBy` inside a
view-level kanban/calendar/gallery/timeline/gantt object literal, lit control
`groupByField` in the same position returning 58 sites — found no other runtime
writer. The only reader that listed it, `ListView`'s projection/expand
collectors, offers `groupByField` first and gets the identical value; the
capability gate and the kanban render branch never read it at all.
Also removes the only producer feeding a latent override: `ListView` spreads
the rest of the merged config after its own `groupBy: laneField`, so a
surviving `groupBy` won over the lane just resolved. The override itself is a
`plugin-list` change and is untouched here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-justin
marked this pull request as ready for review
September 7, 2026 14:40
os-justin
enabled auto-merge
September 7, 2026 14:40
os-justin
deleted the
claude/issue-8213-objectview-groupby-third-lane
branch
September 7, 2026 14:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #8213
ObjectView.kanbanViewOptionswrote{ groupBy: lane, groupByField: lane }into the view-level kanban config it hands tolist-view.groupByis a third spelling of one concept, and@objectstack/spec's strictKanbanConfigSchemarefuses it by name. This drops the write. PM ruling on the card's two options: A.The four probes, re-run on this branch's own base
Measured against the installed
@objectstack/spec17.3.0 (the card measured 17.2.0; objectui#7685 has since moved the pin, so the whole measurement was re-taken rather than quoted).CONTROL fires and CONTROL2 accepts on the same call shape, so the refusal of
groupBycarries information. Key recognition is read offunrecognized_keysonly; the missing-required-columnsinvalid_typeis present in every arm including CONTROL2 and is not a verdict. Upstream names the other alias by name and knows nothing aboutgroupBy.The producer census (the card's binding precondition), sites printed
Instrument: a structural scan of all 6,628 tracked text files — balanced-brace extract of every
kanban/calendar/gallery/timeline/ganttobject literal, searched for a baregroupBy(negative lookahead, sogroupByFieldandgroupBy2do not match). Not a count: every hit printed and classified.Lit control on the same instrument —
groupByFieldin the same position: 58 sites. The empty reading below is therefore a reading.Real view-level
kanbanbag hits, all of them:packages/app-shell/src/views/ObjectView.tsx:407packages/plugin-list/src/__tests__/ListView.kanbanOptionsBagCanonical-8193.test.tsx:107,146packages/data-objectstack/src/updateView.draft.test.ts:38Everything else the raw grep returns is node-level
object-kanban.groupBy(its own declared, required key), ObjectQL chart aggregates (aggregate.groupBy), the grid shorthandschema.groupBy, or i18n labels. The sibling producerdefaultKanbanFromObjectemits{ groupByField }alone and always has;CreateViewDialogemitskanban: { groupByField }.Verdict: the ruling survives the census. No producer emits
groupByand nothing else, so thev.groupByrung inListView's projection/expand collectors is covering nothing this repo writes — and the same expression writes the identical value undergroupByField, which those collectors read first. The capability gate resolvesgroupByField || groupFieldon both nestings and never read it; the render branch resolvesgroupByField || groupField || detectStatusField(...)and never read it either.What the pin asserts, and why it is not a source-text snapshot
packages/app-shell/src/views/ObjectView.kanbanGroupByRetired-8213.test.tsx. Every arm runs the producer and hands the returned object toKanbanConfigSchema. Nothing grepsObjectView.tsx, so a reformat or a rename of the locallanecannot make it lie in either direction.titleFieldandcardFields, andKanbanConfigSchema— which declares exactlygroupByField/summarizeField/columns— refuses both of those by name as well:So the pin asserts the honest form: the refused set is EXACTLY the two known residuals, plus, separately, that the lane keys present on the bag are exactly
['groupByField']. That reddens forgroupByand for any fourth undeclared key, and it stays green for a key that becomes properly declared. The two residuals are a different question and stay:cardFieldsis a declared deprecated alias of the spec'scolumnsin this repo's ownKanbanConfigmirror;titleFieldis live and undeclared anywhere (filed separately — see below).The
...restKanbanoverride: measured, and NOT pinned from this producerThe brief warned that a naive override pin cannot fail here. It is worse than that — no override pin can be driven from this producer: every bag
kanbanViewOptionscan build hasgroupByandgroupByFieldholding the same value by construction, so the two spellings are indistinguishable from this side in every world.A distinguishing fixture exists only one package over, and I built and ran it rather than reasoning about it.
options.kanban = { groupBy: 'LANE_FROM_STRAY_GROUPBY' }againstkanban = { groupByField: 'LANE_FROM_CANONICAL' }, captured off the generatedobject-kanbannode:The override is real and active, not merely latent — it is latent only in the sense that nothing was feeding it a differing value.⚠️ So one line of the ruling needs correcting: dropping this write removes the only producer that fed the override; it does not remove the override, which stays reachable from author-written
kanban.groupByriding this repo's.passthrough()mirror. Filed as aplugin-listcard. The probe file was deleted; nothing temporary is committed.Ablation — the read site, by state
Restored
{ groupBy: lane, groupByField: lane }, proved it reached disk, ran, restored by state. Script carriedtrap '<restore>' EXIT INT TERMwith absolute paths, from the committed implementation.Six rows red, by name:
Every CONTROL stayed green in both worlds (21 passed under ablation) — the schema refusing a bogus key, the schema accepting the canonical config,
groupBystill undeclared, a lane really resolving, absence staying absence. Restore proved by state, not by exit code:Fixture triage, not a spelling sweep
ObjectView.kanbanLane-8193.test.tsx— theMEASURED, NOT FOLDEDarm asserted the producer still wrotegroupBy. Replaced, not re-spelled: the spec-refusal half is still true and stays; the producer half moved to the new pin. Its anti-fork arm carried.filter((k) => k !== 'groupBy')before comparing the two producers — removed, which makes that arm stronger (it is one of the six rows the ablation reddens).ListView.kanbanOptionsBagCanonical-8193.test.tsx— its fixture called itself "the producer's real output" withgroupByin it. That claim is now false. The producer claim moved to the real shape and thegroupBy-bearing bag became an explicit CONTROL (stored metadata written before this change still carries the key, and the mirror is.passthrough()). Split into twoitblocks — a secondkanbanOfferedin one test renders twice into the shared screen and makes everyqueryByRoleambiguous; that is a real red I hit and fixed, not a theoretical one.Verification (all at
8c8ffb467, clean tree)vitest run packages/app-shell/Test Files 648 passed (648)/ `Tests 6246 passedvitest run packages/plugin-list/Test Files 71 passed (71)/Tests 884 passed (884)type-check(both packages, incl.tsconfig.test.json)packages/app-shell type-check: Done/packages/plugin-list type-check: Done, exit 0lint(package-scopedeslint ., both packages)✖ 2923 problems (0 errors, 2923 warnings), exit 0 — warnings are the pre-existingno-explicit-anybaseline; the two on the new file match the pattern the sibling pin already usescheck:control-bytes✅ check-control-bytes: OK (scanned 6635 tracked text file(s); skipped 85 binary).check:spec-symbols✅ spec symbol derivation: 1353 files scanned against 5050 spec export namescheck:unreferenced-sourcesOK Every shipped source file in every covered package is reachable.check-changeset-presence✅ 4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)check-changeset-no-major✅ No changeset declares a \major` bump.`check-governed-queue-guard --test <diff paths>✅ NOT GOVERNED — 5 path(s) checked against 5 governed surface(s); none matched.Dependency closures were built first (
pnpm --filter '@object-ui/app-shell^...' --filter '@object-ui/plugin-list^...' build, exit 0), so the type-check reading is not the stale-distsignature.Changeset
minor(this repo never declaresmajor; a breaking change shipsminor). The changeset argues the removal explicitly: a consumer that did readoptions.kanban.groupByloses the key, loses nothing by it — the identical value is written by the same expression undergroupByField, no gate or render branch ever read it — and anyone who authored the key was already off-spec, since the platform refuses it at authoring and publish time.Out of scope, filed separately
plugin-list: therestKanbanspread overrides the lane the kanban branch just resolved — measured above with a distinguishing fixture.groupByis the one key the destructure does not strip.titleFieldis undeclared on both faces — refused by the spec'sKanbanConfigSchema, accepted only by this repo's.passthrough()mirror, and live (ListViewforwards it onto the generated node). UnlikecardFieldsit is not declared as a deprecated alias anywhere. Either promote it upstream or declare it in the mirror the wayCalendarConfig.defaultViewis.data-objectstack/src/updateView.draft.test.ts:38authorskanban: { groupBy: 'status' }in a draft-view payload fixture — off-spec metadata, inert for that test's subject, and no longer what any producer writes.API rate limit already exceededfor issue search while this ran, so the dedup search these three need could not be completed. They are described in full in the report for the PM to file rather than filed unsearched — filing without the dedup read and filing nothing are both forbidden.Scope fence held
View-level
KanbanConfigonly. The node-levelObjectKanbanSchematombstones (objectui#7322 / #8172 / #7742 / #7772 / #8174) are untouched, and the live view-levelgroupFieldalias objectui#8193 deliberately left in this same expression is untouched — its read and its write both stay. Branch cut from currentmain(614d85b19); nothing was carried over from a card's numbers.🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code