Skip to content

fix(sdui-parser): braced non-JSON values draw an inert-expression warning instead of vanishing silently - #6613

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6598-html-tier-declared-inputs
Aug 27, 2026
Merged

fix(sdui-parser): braced non-JSON values draw an inert-expression warning instead of vanishing silently#6613
os-sales merged 1 commit into
mainfrom
claude/issue-6598-html-tier-declared-inputs

Conversation

@claude

@claudeclaudeBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Part of #6598

What the card asked, and what measurement answered

The card's mechanism claim was "columns never reaches the renderer because the registration's declared inputs omit it", with a directive to confirm on the live registration and to report the fork if it is declared. Measured on origin/main (base 574c33f):

  1. The block is list-view, not object-table. The original report (objectstack-ai/objectstack issue 12649, which this card was moved from) names list-view literally. object-table has no live registration anywhere in this repo — it exists only in sdui-parser test fixtures and one plugin-dashboard test. The object-table spelling entered at triage; the tier.test.ts fixture the card anchored on is synthetic.
  2. The live list-view registration declares columns ({ name: 'columns', type: 'array' } in packages/plugin-list/src/index.tsx), plus objectName (required), viewType, filter, sort, options. It declared columns at the reporter's 17.1.0 too (verified at release commit 620f618). The registration is not systematically short.
  3. The declared-inputs hypothesis could never produce this symptom: parse.ts keeps every attribute, and validateTree only emits warning-severity unknown-prop for undeclared ones. Compile never strips a prop, declared or not.

The real mechanism — the fork the card authorised

interpretBrace materializes strict-JSON braced values only. Anything else — the single-quoted array every JSX author writes (columns={['name','amount']}), unquoted object keys (columns={[{field:"name"}]}), any JS expression — compiles to the deferred { $expr } marker, and nothing downstream evaluates that marker: this tier parses, never executes (ADR-0080), and a repo-wide grep finds zero $expr consumers outside sdui-parser. The marker reaches the renderer as an opaque object; ListView's defensive non-array read degrades it to "no declared columns"; rows still fetch via objectName. Result: row count + toolbar + index column, zero data columns, zero diagnostics anywhere — ADR-0078's prohibited parsed-but-silently-inert state, and exactly the production symptom.

Live-path reproduction (compile against the registry-built manifest exactly as page.tsx builds it, rendered through SchemaRenderer with the real plugin-list registration, a stub object-grid recording the handoff):

  • columns={["name","amount"]} (strict JSON): zero diagnostics, grid receives ['name','amount'] — the pipeline is lossless for JSON spellings on main and at 17.2.0 (ca58e45).
  • columns={['name','amount']} (single quotes): zero diagnostics, tree carries { $expr: "['name','amount']" }, grid receives columns: [], rows still fetched — the silent vanish.
  • columns='["name","amount"]' (JSON-string form): warning-severity type-mismatch at compile, [] at the grid — silent at runtime.
  • Undeclared aggregate (the maintainer's follow-up): kept in the tree, draws unknown-prop (warning) at compile, read by no renderer — its loud half already exists; nothing to declare (the grid vocabulary is aggregations).
  • The react-tier contrast: kind:'react' folds real evaluated JS props into the schema node (react-page.tsx scope wrappers), so the same spelling works there — which is why the reporter saw the tier split.

Version archaeology: at 17.1.0 (620f618) the registry path had the pre-#3144 defect (no data-source adapter reached a bare-registered list-view at all), so the production environment was running a later console bundle; from 17.2.0 through main the JSON spellings work and the non-JSON spellings vanish silently. The $expr hole exists at every version measured.

The fix (deliberately the loud half only)

validateTree now emits a warning-severity inert-expression diagnostic when a declared input's value is the $expr marker, message carrying the fix (write the value as JSON, double-quoted strings and keys). Warning, not error, per the objectui#5709 posture for inert authored keys: pages keep compiling and rendering exactly as before — the silence is what changed. Pinned by packages/sdui-parser/src/__tests__/inert-expression-6598.test.ts, including "strict-JSON spellings stay diagnostic-free".

Reverse verification: with validate.ts reverted to base (fix committed first, revert via git checkout from base, restore via git checkout from HEAD, git diff HEAD empty after), the three inert-expression pins go red (3 failed, 2 passed) and return green on the restored tree (5 passed).

Deliberately NOT decided here — maintainer contract decisions

Each of these changes what the html tier accepts, so none is taken unilaterally; they are escalated in the dev report on the issue:

  • Escalating inert-expression to error severity (would flip compile accept/reject for pages carrying inert expressions today).
  • Widening the accepted literal grammar — materializing single-quoted strings / unquoted identifier keys so the universal JSX spelling just works (my recommendation, argued in the report).
  • Base-prop coverage — a $expr on style (BASE_PROPS) is equally inert and currently skipped before the input lookup.
  • The sister parser copy — the framework repo carries a hoisted sdui-parser whose save gate runs parse-only (no manifest wired), so this diagnostic reaches it only when that repo syncs its copy and wires the manifest; grammar decisions must move both copies in lockstep or save-vs-render dialects split.

Out of scope, untouched: issue 6458 remains open (the four-key adjudication on ObjectGrid authored-column reads — format / options / appearance / essential not touched; the new columnReadBoundary guard from PR 6587 is unaffected: no plugin-grid file changed). Issues 5905 and 6150 remain open; the manifest-wiring gap above is 5905-adjacent context, not addressed here.

Gates (all at HEAD f62a923, after the final commit)

  • pnpm exec vitest run packages/sdui-parser/ — 10 files, 82 passed (includes the 5 new pins).
  • pnpm --filter @object-ui/sdui-parser run type-check — clean; tsc -p tsconfig.test.json --listFiles confirms the new test file is in the checked closure (1 hit).
  • pnpm --filter @object-ui/sdui-parser run lint — 0 errors, 7 pre-existing warnings all in untouched files (render.test.tsx, verify.ts); the changed files lint 0/0. Narrowing note: repo lint is turbo run lint (per-package eslint units); the changed package's whole unit ran; the diff touches no other package's files.
  • Consumer sweep (compile/manifest consumers + html-kind render tests): 9 files 232 passed, 4 files 22 passed — run pre-commit on identical content.
  • node scripts/check-control-bytes.mjs — OK (5463 files).
  • Changeset: .changeset/6598-inert-expression-warning.md (@object-ui/sdui-parser: minor; no major, per repo version policy).

Generated by Claude Code


Generated by Claude Code


Generated by Claude Code

…ning instead of vanishing silently
A braced attribute value that fails JSON materialization compiles to the
parser's deferred { $expr } marker, and nothing downstream evaluates that
marker — the html tier parses, never executes (ADR-0080), and no renderer
consumes $expr. The value reached the renderer as an opaque object, defensive
non-array reads degraded it to 'not declared', and the author's binding
vanished with zero diagnostics anywhere (ADR-0078's prohibited
parsed-but-silently-inert state). validateTree now names it at compile time,
warning severity, with the fix in the message.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3236.2 KB3266.6 KB
Main entry chunk (gzip)157.1 KB350 KB
Entry fileindex-B1C4lCSS.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)11.89KB4.50KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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.01KB114.64KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)238.89KB60.02KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.85KB12.89KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.48KB34.49KB
plugin-designer (index.js)212.80KB43.15KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)131.78KB32.19KB
plugin-gantt (index.js)165.16KB40.33KB
plugin-grid (index.js)201.66KB54.58KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)112.86KB27.54KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.72KB7.71KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)85.87KB21.12KB
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.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)65.97KB21.98KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)9.30KB3.22KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-sales@claude