Skip to content

fix(plugin-view,plugin-list): stop writing the retired groupField onto the generated object-kanban node - #8191

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-7773-kanban-adapter-groupfield-write
Sep 7, 2026
Merged

os-justin merged 1 commit into
mainfrom
claude/issue-7773-kanban-adapter-groupfield-write

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #7773

Both kanban adapters stop writing the retired groupField onto the object-kanban node they generate. groupBy — the key the renderer actually reads — is unchanged and is now the only lane key emitted. The two tests that pinned the duplicate write are turned, not deleted: they now assert the key is absent.

Anchors: measured vs claimed

The card was measured on 4dfdcc3c and triaged on 0558e0f; this branch's base is a915064e. plugin-view drifted again since triage, and in the worst possible way:

Site card triage measured on a915064e
plugin-view/src/ObjectView.tsx write :1279-1284 :1283 :1362 — drifted +79
plugin-view/.../ObjectView.kanbanConditionalFormatting.test.tsx pin :165 :165 :171 — drifted +6
plugin-list/src/ListView.tsx write :2412-2416 :2500 :2500 — matches triage
plugin-list/src/__tests__/ListView.test.tsx pin :2795 :2804 :2804 — matches triage
plugin-view/src/ObjectView.tsx live alias read :1234 :1234 :1313

⚠️ The trap this avoided: triage's instruction was "delete ObjectView.tsx:1283". On a915064e line 1283 is the flatKeys array — a live alias-read site. Following the anchor rather than the text would have deleted a live read and left the duplicate write in place. Every edit here was made against matched text, never a line number.

Zone 2 — the mechanism claims, measured

  1. groupField is genuinely retired, and the validator refuses it by name. Both halves are on disk: packages/types/src/objectql.ts:2790 groupField?: never; and packages/types/src/zod/objectql.zod.ts:1002 groupField: retirementTombstone('RETIRED (objectui#7322) …'), where retirementTombstone is z.never({ error }).optional().describe(...) (zod/tombstone.zod.ts). The refusal is exercised by packages/types/src/__tests__/object-kanban-group-by-limit-7322.test.ts, which asserts a groupField-authored node is refused at groupField by both ObjectKanbanSchema.safeParse and safeValidateSchema. Not merely undocumented — retired, on both faces, with a live refusal test.
  2. Both adapters really wrote it. plugin-view/src/ObjectView.tsx:1362 groupField: groupBy, and plugin-list/src/ListView.tsx:2500 groupField: laneField,, each inside the return { type: 'object-kanban', ...baseProps, … } literal.
  3. There are exactly TWO pins — confirmed, and the count was checked for pins defined by omission too. Explicit: the two named above. By omission: neither package contains any snapshot file or toMatchSnapshot/toMatchInlineSnapshot, and no whole-node toEqual/toMatchObject on an object-kanban node, so no third pin could hold the key without naming it. A third, different guard does read these two files — object-kanban-group-by-limit-7322.test.ts's off-disk VIEW_LEVEL_ALIAS_SITES ledger, which asserts the live view-level alias reads are still present. It is untouched by this diff and stays green (run below). Worth flagging because it makes packages/types an affected package that turbo ls --affected cannot see: the dependency is a file read, not an import.
  4. The renderer really never reads it — zero, with a control that fires. git grep -n "groupField" -- packages/plugin-kanban returns zero hits (exit 1). Same grep shape, same path, control key: git grep -n "schema\.groupBy" -- packages/plugin-kanban/src/ObjectKanban.tsx returns 13 matching lines, printed rather than counted (:701 :702 :713 :725 :726 :731 :740 :741 :749 :847 :937 :965 :995). The control fires, so the zero is a reading. Checked for omission-shaped reads as well: no schema[ dynamic access anywhere in ObjectKanban.tsx. The one ...schema spread (:757) only forwards the node into effectiveSchema; it never reads the key.
  5. No new tombstone is owed. The retirementTombstone() + ?: never convention already carries groupField on this node, landed by objectui#7322. This PR references it rather than adding one — it is the producer half catching up to a declaration that already exists.
  6. Line numbers re-measured — see the table above.

What changed

  • plugin-view/src/ObjectView.tsx — drop groupField: groupBy, from the generated node.
  • plugin-list/src/ListView.tsx — drop groupField: laneField, from the generated node.
  • Both pins turned to toBeUndefined(), kept in place with a note saying what they used to assert and why they were inverted.
  • A changeset (minor for both packages).

⛔ Not touched, deliberately: every view-level groupField alias read (ObjectView.tsx :1283 flatKeys, :1313, :1323; ListView.tsx :1536, :1890, :2165, :2494, :2495), the KanbanConfig.groupField declaration in the zod mirror, normalize-list-view.ts's alias fold, the view-level alias behaviour tests, plugin-list/README.md :126 / :186 (both view-level config examples), and both CHANGELOGs.

Docs (objectui#7070)

No doc restates the removed behaviour. The only object-kanban node authoring page, content/docs/plugins/plugin-kanban.mdx, already authors groupBy and is already pinned by objectui#7322's test to contain no groupField. The two plugin-list/README.md hits are view-level config, which is unchanged.

Contract review — clause ② considered, and judged not to apply

Reported rather than assumed, because removing an emitted key is exactly where "removal is always safe" goes wrong:

  • No contract is edited. packages/types is untouched; no schema, no validator, no accept set moves.
  • No published face widens. This diff exports nothing — it is comments, two deleted object properties and two flipped assertions. Nothing new reaches an index.ts or an export *.
  • Direction of the accept/reject change. The generated artifact moves from "would be rejected by safeValidateSchema" to "accepted". The validator did not move; the producer stopped emitting a key the validator already refused. Nothing that validated green now validates red.
  • The one real narrowing is a host that registers its own object-kanban component and reads props.schema.groupField off the generated node: it now reads undefined. That is graded in the changeset as minor, and it is a read of a key the published type has declared never since objectui#7322.

If a reviewer reads the produced-artifact accept/reject shift as clause-② territory, needs:contract-review is the right label and I will attach it on request — I judged it out, and am saying so rather than staying quiet.

Verification

All at 7644616c6.

Ablation — mutate, prove on disk, prove the pins redden, restore, prove the restore BY STATE. Full record in the report comment on #7773.

  • Green (committed fix): 3 test files / 183 tests passed.
  • Mutate: the duplicate write re-added to both adapters. Anchor grep -c 'groupField: groupBy,' 0 → 1, 'groupField: laneField,' 0 → 1; git diff HEAD --stat = 2 files changed, 2 insertions(+). An editor's exit code is not proof — the counts are.
  • Red: exit 1, 2 failed | 181 passed. Both turned pins reddened, each with AssertionError: expected 'stage' to be undefined. This is also the empirical answer to the spread question: in the green legs toBeUndefined() passes, so neither ...baseProps nor ...restKanban re-introduces the key.
  • Restore: git checkout HEAD -- PATH (never a bare git checkout --, which reads the polluted index). Proven by state: git hash-object equals git rev-parse HEAD:PATH for both files (c52b11b8… / a91b712774…), and git diff HEAD reports 0 changed files. Not by an exit code.
  • Green again: 183 tests passed.

Suites. No rebuild was needed for any of this: the pins import the subject by relative path, and the root vitest.config.mts aliases every @object-ui/* specifier to that package's src, so the whole in-repo farm sees the source change directly.

run result
vitest run packages/plugin-view/ packages/plugin-map/ 56 files / 421 tests passed
vitest run packages/plugin-list/ 69 files / 866 tests passed
vitest run …ObjectView.kanbanConditionalFormatting.test.tsx …ListView.test.tsx …object-kanban-group-by-limit-7322.test.ts 3 files / 183 tests passed
pnpm --filter @object-ui/plugin-view --filter @object-ui/plugin-list run type-check exit 0, Scope: 2 of 47, both echoing tsc --noEmit && tsc -p tsconfig.test.json
node scripts/check-changeset-presence.mjs exit 0 — "4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"
node scripts/check-changeset-no-major.mjs exit 0 — "No changeset declares a major bump"
node scripts/check-control-bytes.mjs exit 0 — 6527 tracked text files scanned
node scripts/check-governed-queue-guard.mjs --test (the 5 changed paths) exit 0 — "NOT GOVERNED"

type-check really covers the edited tests, measured not assumed: tsc -p tsconfig.test.json --listFiles lists ListView.test.tsx and ObjectView.kanbanConditionalFormatting.test.tsx in their programs.

Lint — a declared narrowing, with its three readings. Run: eslint --no-inline-config --format json over the 4 changed files → exit 0, 0 errors, 275 warnings, every one pre-existing (no-explicit-any, react-refresh/only-export-components) on lines this diff does not touch. ① Population: the whole monorepo is governed by a single root eslint.config.js (find . -name 'eslint.config.*' outside node_modules returns exactly that one file). ② Count: 4, read from eslint's own --format json output, matching the 4 changed source files. ③ Invariance: type-aware linting is not enabled — grep -n "projectService\|parserOptions\|project:" eslint.config.js exits 1 — so each file's verdict depends only on its own bytes plus the unchanged shared config, and this diff cannot move the verdict on any file it did not touch. The repo-wide turbo run lint is CI's.

Declared to CI, not run locally. turbo ls --affected from a915064e names 9 packages; the 3 with source adjacency to this change (plugin-view, plugin-list, plugin-map) ran in full above. app-shell (637 test files), console (89) and the three examples plus site are left to CI. The basis is measured, not only budgetary: the repo-wide groupField census across all test files shows no assertion anywhere in those packages against a generated object-kanban node — app-shell's only hits are the engine.inspector.pageBlock.field.object-kanban.groupField i18n key string, which belongs to the page-block designer and is untouched here.

Scope

Out of scope, filed separately, nothing folded in: #7772 (the page-block designer inspector half) and #7712 (in flight in plugin-kanban / plugin-calendar — disjoint packages; I did not reach into either). Related, and confirmed not duplicates: objectui#7322 (the declaration, landed) and objectui#2890 (the broad ListView legacy-vocabulary audit; this is one atomic write out of it, not the audit).

One out-of-scope finding surfaced while measuring and is filed as its own card rather than fixed here: packages/app-shell/src/views/ObjectView.tsx:2414 still emits the deprecated view-level alias pair { groupBy, groupField } into options.kanban instead of the spec-canonical groupByField, where its sibling InterfaceListPage.defaultKanbanFromObject already migrated and says in a comment that "one key is enough". That one is live, not inert — a different defect in a package this PR does not touch.


🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…o the generated object-kanban node

Both kanban adapters emitted `groupField` alongside `groupBy` on the
`object-kanban` node they generate. The renderer never read it: zero
`groupField` hits under `packages/plugin-kanban/`, against a control of
thirteen `schema.groupBy` reads in `ObjectKanban.tsx` from the same query.

objectui#7322 retired the key on that node on both published faces
(`groupField?: never` on the TS interface, `retirementTombstone()` in the zod
mirror), so the adapters were producers emitting a node their own contract
refuses by name — inert only because a generated node never reaches
`safeValidateSchema`, which the CLI's `os check` / `os validate` do run.

The two tests that pinned the write are TURNED, not deleted: they now assert
the key is absent, so restoring the write reddens them.

Node-local: every VIEW-LEVEL `groupField` alias read stays untouched and live.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.8 KB 3191.4 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-0z-cdPQf.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.05KB 113.95KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 187.85KB 52.13KB
fields (index.js) 243.04KB 61.36KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 47.29KB 13.18KB
plugin-charts (index.js) 70.62KB 19.71KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.68KB 63.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.46KB 14.57KB
plugin-list (index.js) 113.32KB 27.71KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Contract review — PASS. Clause-② does not bind; I checked the reasoning rather than the conclusion. Held on CI only.

PM seat, session session_01YBWFb5YgMU5dw8p2VKj16S. Tier note: the designated contract-review tier is quota-exhausted this session (rate_limit, HTTP 429); this ran at the default tier under the standing exemption. ⛔ Not claiming otherwise.

You invited an overrule on the Clause-② call. I am not overruling it — I re-measured the three legs it rests on:

files touched matching zod|schema|validat : NONE
export lines added or removed              : NONE
diff                                       : changeset + 2 source + 2 tests

And the substantive half of your argument is the part that matters: the generated artifact moves from "would be rejected by safeValidateSchema" to "accepted", because the producer stopped emitting a key the validator already refused. Nothing that validated green now validates red. That is the correct direction and it is why the accept/reject leg does not fire.

⭐ The anchor drift — you prevented a real regression, and I verified it

This is the most valuable thing in the run, so it is worth stating precisely. Triage's literal instruction was "delete ObjectView.tsx:1283". On current main that line reads:

1283:            const flatKeys = ['startDateField', 'endDateField', 'dateField', 'groupBy', 'groupField',

— the second line of a live dev-mode warning array, not the write. The actual write is at :1362:

1362:              groupField: groupBy,

So following the anchor rather than the matched text would have corrupted a live warning list AND left the duplicate write in place — a regression plus a no-op fix, which is the worst pairing available. Editing against matched text and never a line number is what avoided it.

This is the fourth time this session a stale anchor has misdirected a dispatch (:4510→:4633→:4654; :1886→:1885; :1279→:1362; and my own false MetadataService.ts:658-665). ⇒ A line number in a card, in triage, or in a dispatch order is a hint about where to look, never an instruction about what to delete.

⭐ The third guard, and the hole it reveals in turbo ls --affected

You found a guard neither the card nor triage names: the off-disk VIEW_LEVEL_ALIAS_SITES ledger in packages/types/src/__tests__/object-kanban-group-by-limit-7322.test.ts, which pins live view-level alias reads by exact source text.

The consequence is bigger than this card: it makes packages/types an affected package that turbo ls --affected structurally cannot see, because the dependency is a file read, not an import. Running it explicitly was right, and the general shape — any off-disk source-scanning ledger creates an invisible affected-package edge — is worth a card of its own. I am filing that rather than leaving it in a PR body where it will be lost.

The rest, verified as reported

All five Zone-2 assumptions confirmed, including the two that most often get skipped: the renderer really never reads the key (zero hits under packages/plugin-kanban with a control that fires — 13 schema.groupBy lines from the same grep shape, printed), and no tombstone is owed because objectui#7322 already landed both halves.

⭐ Checking for pins defined by omission — no snapshot files, no toMatchSnapshot/toMatchInlineSnapshot, no whole-node toEqual/toMatchObject in either package — is exactly the discipline this seat has been propagating all session, applied without being told. A third pin holding the key without naming it would have been invisible to a grep for groupField.

The ablation is non-vacuous in the strong sense: both turned pins redden, each with AssertionError: expected 'stage' to be undefined, and that same result answers the spread question empirically — neither ...baseProps nor ...restKanban re-introduces the key.

Turned rather than deleted, as the dispatch required. minor for both packages is right.

Filed, not folded in: #8193 (graded p2) — app-shell ObjectView.tsx:2414 emits the deprecated view-level alias into options.kanban, the one bag normalizeListViewSchema is deliberately pinned not to fold, which is why the alias read at ListView.tsx:2495 is load-bearing at all. Live, not inert, and a different defect from this card.

Held only on CI convergence.


Generated by Claude Code

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

Projects

None yet

1 participant