Skip to content

fix(plugin-view): a top-level conditionalFormatting no longer reaches the kanban view (#5248) - #5336

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-5248-conditional-formatting-kanban-read
Aug 19, 2026
Merged

fix(plugin-view): a top-level conditionalFormatting no longer reaches the kanban view (#5248)#5336
os-support-ai merged 1 commit into
mainfrom
claude/issue-5248-conditional-formatting-kanban-read

Conversation

@os-support-ai

@os-support-aios-support-ai commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes#5248

Branch fired: Option 2. The liveness check that gates the 2026-08-19 ruling came back empty — no authored document in either repo puts conditionalFormatting on an object-view node — so the (schema as any).conditionalFormatting fallback in generateViewSchema's kanban branch is dropped rather than the key declared. The object-view node's 27 host-composition keys (objectui#5097) are now read only inside the #region fence, so that exemption's stated basis — "reachable only via the host-supplied renderListView delegation" — is true for all 27 without exception. Option 1 (declare the key on ObjectViewSchema + registry inputs) was pre-ruled for the other outcome and did not fire; Option 3 was explicitly rejected by the ruling and is not implemented.

Clause-②: no. This branch narrows — a key stops being honoured on the author path, restoring declared = enforced. It does not widen the accepted set. (Option 1 would have been yes; it did not fire.)

The liveness check — commands and output

The ruling's "(expected)" about an empty result is a prediction, so it was treated as something to falsify. Every zero below is paired with a counter-probe run through the same method on the same corpus with a term known to be present.

objectui — authored corpora (content/docs, skills, examples, apps):

$ grep -rn "conditionalFormatting" content/docs skills examples apps | grep -v node_modules
content/docs/plugins/plugin-grid.mdx:73:| `rowHeight`, …, `conditionalFormatting`, … | | The rest of the declared surface. |
skills/objectui/guides/schema-expressions.md:283:> typed as CEL (`ListViewSchema.conditionalFormatting[].condition` and
skills/objectui/guides/schema-expressions.md:291: "conditionalFormatting": [

Two files, and neither is the case that fires the fallback: the first documents object-grid's declared key, the second authors it on a list-view node ({ "type": "list-view", "objectName": "invoice", "conditionalFormatting": [...] }) — also declared. Zero top-level occurrences on an object-view node.

Controls, same method, same corpora: object-view → 5 files (docs 3, examples 1, apps 1); groupByField → 1 file. Non-zero, so the probe reaches these trees.

objectui — remaining dirs (e2e, scripts, public, docs): one hit, docs/audits/2026-07-objectview-detailview-schema.md:92, prose listing the forwarded keys. Control objectName in the same dirs → 13 files.

objectstack (the sibling repo where the real metadata apps live):

$ grep -rn "conditionalFormatting" content/docs skills examples | grep -v node_modules
content/docs/references/ui/view.mdx:520 / :609 (declared surface tables)
content/docs/references/ui/component.mdx:287 / :329 (declared surface tables)
examples/app-showcase/src/ui/views/field-zoo.view.ts:61: conditionalFormatting: [

The one real authored use, field-zoo.view.ts:61, sits inside defineView({ list: { type: 'grid', … } }) — a view-level rule on a grid view. That is the activeView?.conditionalFormatting link, which this PR keeps.

More decisively, an object-view node is not authored in objectstack at all:

$ grep -rl "object-view" --include='*.ts' --include='*.tsx' --include='*.json' --include='*.yml' \
--include='*.md' --include='*.mdx' . | grep -v node_modules | grep -v /dist/ | grep -v CHANGELOG
./packages/spec/liveness/view.json (prose in a ledger note)
./docs/adr/0080-ai-authored-ui-jsx-source.md (prose)

Controls in that same corpus: object-form → 54 files, object-grid → 19 files. So the zero is a reading, not an artefact of the method.

Structural cross-check — every file in either repo containing both an object-view mention and conditionalFormatting: packages/types/src/objectql.ts and .../zod/objectql.zod.ts (declarations), plugin-view/src/ObjectView.tsx + its pin test (the code under change), app-shell/src/views/ObjectView.tsx, ROADMAP.md and one audit doc (prose), plus objectstack's packages/spec/liveness/view.json. The app-shell one is the host: it builds its object-view node at ObjectView.tsx:2154without the key, and reads conditionalFormatting at :1962 into the delegated list-view node inside its renderListView callback. Nothing authored relies on the top-level read.

Supporting facts re-measured on this base: conditionalFormatting is not a member of ObjectViewSchema (0 hits inside the interface in packages/types/src/objectql.ts), and it is not among the 15 names in the object-view registry inputs (packages/plugin-view/src/index.tsx).

What changed

  • packages/plugin-view/src/ObjectView.tsx — the kanban branch's chain loses its third link. It is now kanbanCfg.conditionalFormatting ?? activeView?.conditionalFormatting; the comment records the ruling, the liveness evidence and why the fallback must not come back.
  • Same file — the OBJECT_VIEW_HOST_COMPOSITION_KEYS block's "The one asymmetry" section is rewritten as "RESOLVED (objectui#5248)", carrying the liveness evidence, and the #region banner is updated to match.
  • packages/plugin-view/src/__tests__/objectViewHostSurface.test.tsx — the pin that read .toEqual(['conditionalFormatting']) now asserts .toEqual([]): zero exempt keys read outside the fence. A second, named test pins the same fact for this key specifically, plus its continued read inside the fence.
  • packages/plugin-view/src/__tests__/ObjectView.kanbanConditionalFormatting.test.tsx — new; 7 tests.
  • Changeset: @object-ui/plugin-viewminor (behaviour change, described as one; never major per the repo's version-alignment rule).

Not narrowed: the host renderListView delegation still reads the key off the object-view node and forwards it to the host's list renderer. It stays host-composition surface; a stored app-shell document that carries it keeps rendering. That half is asserted in the new test file so the two cannot drift apart.

Tests — measured at 6325ef257

$ pnpm exec vitest run packages/plugin-view/ # repo root, CI's config
Test Files 17 passed (17)
Tests 180 passed (180)
$ pnpm exec turbo run type-check --filter=@object-ui/plugin-view --concurrency=2
Tasks: 16 successful, 16 total
$ pnpm exec eslint [the 3 changed source files] # 0 errors, 65 pre-existing-style warnings
$ pnpm run check:control-bytes # OK (4726 tracked text files)
$ node scripts/check-changeset-presence.mjs # OK, 1 changeset
$ node scripts/check-changeset-no-major.mjs # OK

New behaviour pinned (all 7 green): a top-level rule does not reach the emitted object-kanban node (no own property, undefined); the view-level rule still does; the nested options.kanban rule still does and still wins; a top-level key neither displaces the declared links nor fills in for a view that declares none; a control that groupBy/groupField/titleField/cardFields still flow (without it, "emits nothing" would pass too); and the host renderListView delegation still receives the top-level key on a list-view node.

Reverse verification — predicted before running, then observed

Legs: restore ObjectView.tsx to origin/main, keep both test files; then restore to the commit.

Build artifact between the edit and the thing under test: none, on either leg. The repo-root vitest config aliases every @object-ui/* specifier straight at packages/*/src (vitest.config.mts lines 244-285, including @object-ui/core, @object-ui/types, @object-ui/react), no package in this worktree has a dist/ at test time, and the edited file lives in the package under test and is imported relatively. The pin test additionally reads ObjectView.tsx from disk at import. So no rebuild is owed on either leg — and the red leg landing exactly where predicted is itself the proof that the mutation reached the tests rather than a stale artefact.

Predicted red (4): the new file's "a TOP-LEVEL rule … does not reach the kanban node" and "a top-level key does not fill in for a view that declares none"; the pin's "NO exempt key is read outside the fence" and "conditionalFormatting in particular is no longer read outside the fence". Predicted green: the other 13.

Observed — identical, no surprises:

 × a TOP-LEVEL rule on the object-view node does not reach the kanban node
× a top-level key does not fill in for a view that declares none
× NO exempt key is read outside the fence — the objectui#5248 resolution
× `conditionalFormatting` in particular is no longer read outside the fence (objectui#5248)
Test Files 2 failed (2)
Tests 4 failed | 13 passed (17)

Restoration leg (source back at the commit, tests untouched): Test Files 2 passed (2) / Tests 17 passed (17).


Generated by Claude Code

… the kanban view (#5248)
The kanban branch of `generateViewSchema` resolved its rule list from three
links: `options.kanban.conditionalFormatting`, the active view's own rule, then
`(schema as any).conditionalFormatting` off the object-view node. Only the first
two are declared surface — `ObjectViewSchema` has no such member and the
`object-view` registration does not publish it in `inputs` — yet the third was
honoured, on the path the REGISTERED renderer takes. It was the one
counter-example to the objectui#5097 host-composition exemption, whose stated
basis is that its 27 keys are reachable only through the host-supplied
`renderListView` delegation.
Maintainer ruling 2026-08-19 on #5248 (verbatim 「全部接受」): Option 2, gated on a
liveness check, with Option 1 (declare the key) pre-ruled for real authored
usage. The liveness check came back empty — no authored document in either repo
puts `conditionalFormatting` on an object-view node — so the fallback read is
dropped and the exemption's basis now holds for all 27 keys.
The host delegation is NOT narrowed: it still reads the key off the object-view
node and forwards it, so stored app-shell documents keep rendering. Both halves
are pinned by a new test, and the host-surface pin now asserts that zero exempt
keys are read outside the fence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)25.3 KB350 KB
Entry fileindex-DQZ2HhKb.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.83KB3.70KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.33KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.75KB113.40KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.34KB
fields (index.js)237.07KB59.46KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.42KB1.39KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)29.43KB7.15KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.92KB32.80KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)241.46KB60.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)124.19KB30.20KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)198.22KB53.28KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.66KB27.13KB
plugin-map (index.js)20.08KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.52KB20.67KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)36.10KB12.26KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-support-aiClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — PM review, round 17.

  • Path surface (git diff --name-only origin/main...): 4 files — packages/plugin-view/src/ObjectView.tsx, two test files, one changeset. Entirely inside the declared surface; zero governed-surface hits.
  • Gates: every gate job completed: success — Lint, Type Check, Test shards 1–4, Build & E2E, Build Docs, Doc Snippet / Doc Component Type Check, Changeset Declaration / Bump Policy / Fixed Group, Bundle Analysis, Control Byte Scan, Internal Docs Link Check, Skill Guide Path Check. No cancelled, no in_progress.
  • Option 2 fired, and the host renderListView delegation was not narrowed — its forwarding is pinned. That was the trap in this card and it was avoided.

The liveness check is the card, and this one is a reading rather than an absence

The ruling predicted the empty result and called it "(expected)". A prediction is not evidence, and this was treated as something to falsify. Every zero is paired with a control on the same corpus through the same method:

proberesultcontrol on the same corpus
objectui content/docs + skills + examples + apps: conditionalFormatting2 files, neither a top-level object-view key (plugin-grid.mdx on object-grid's declared key; schema-expressions.md on a list-view node)object-view → 5 files, groupByField → 1 file
objectstack: authored object-view nodes0 (2 prose mentions repo-wide)object-form → 54 files, object-grid → 19 files
objectstack's one real authored conditionalFormattingexamples/app-showcase/.../field-zoo.view.ts:61 — a view-level rule on a grid view, i.e. the activeView link this PR keeps

And a structural cross-check that a keyword sweep alone could not give: no file in either repo carries both an object-view node and the key. The 7 files carrying both mentions are declarations, the code under change, its pin test, app-shell's host (which builds its object-view node without the key and reads the key into the delegated list-view node), and prose.

That is what makes the deletion defensible six months from now, which is the point of the artifact being amended.

Verification

Reverse-verification predicted 4 red and named them before running — two in the new file, two in the pin — and observed exactly Tests 4 failed | 13 passed (17), no surprises and no direction reversal. Restoration leg green at 17/17.

The build-artifact question was answered rather than assumed: the repo-root vitest config aliases every @object-ui/* specifier straight at packages/*/src (vitest.config.mts:244-285), no package in the worktree had a dist/ at test time, the edited file is in the package under test and imported relatively, and the pin test reads ObjectView.tsx from disk at import. The red leg landing on exactly the predicted four is itself the proof the mutation reached the tests rather than a stale artefact.

The OBJECT_VIEW_HOST_COMPOSITION_KEYS "one asymmetry" note is rewritten as resolved, carrying that evidence — so the next census reads a statement the repo supports, which is precisely what #5241's pattern exists for and precisely what #5240's fork showed happens when it doesn't.

Merging via the queue.


Generated by Claude Code

@os-support-ai
os-support-ai added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit d006ce1Aug 19, 2026
22 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-5248-conditional-formatting-kanban-read branch August 19, 2026 16:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-support-ai@claude