fix(components): element:number shows a "no object named" state instead of a silent dash (objectui#10951) - #10989
Conversation
…ad of a silent dash An element:number that authors an aggregate and names neither properties.object nor dataSource.object now draws a short, muted, localized notice (element.number.noObject, in all ten packs) instead of painting the empty dash. Only authored absence qualifies: a binding that names an object keeps its loading and error panels while its view resolves or after it fails, and a node with no aggregate keeps the dash. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
changeset-claim-re-read
|
✅ 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
|
|
|
…10951-element-number-no-object-state Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ 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
|
Contract reviewServed-tier: This is an in-seat review of objectui#10951's runtime half (a): one renderer branch, one locale key in ten packs, a docs sentence, pins and a changeset (15 files, +326 −1), ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10951
Clause-②: no — (a) is renderer-only plus one new locale-pack key, the same shape PR objectui#10101 declared
no: no accept set relaxes, no manifest input moves, and no export is added. (b) did not land (measured below), so the published manifest's declared inputs are byte-unchanged.Implemented on branch
claude/issue-10951-element-number-no-object-stateby theos-devrun dispatched from thedomain:uiseat 2 sessionhttps://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN(claim comment 5870572293 on the card, triage 5869415559).What changed
ElementNumberRenderer(packages/components/src/renderers/basic/elements.tsx): when the node authors anaggregateand names no object in either place (noproperties.object, and nodataSourcewhoseobjectis a non-empty string, which is whatuseElementDataSourcereads as statusabsent), the renderer draws a short muted notice (text-xs text-muted-foreground, test idelement-number-no-object) instead of the silent dash. It queries nothing and does not throw.absent, so a binding that names an object keeps the loading panel while itsviewresolves and the error panel when the view fails to resolve.aggregatestill paints the dash (measured on the base, pinned as a control below).t('element.number.noObject', { defaultValue })throughuseObjectTranslation(the hookelements.tsxalready imports). The key is added to all ten packs (packages/i18n/src/locales/*.ts, newelement.numbernamespace), zh-CN included: 「未指定对象:请设置 object 或 dataSource.object。」. English: "No object named: set object or dataSource.object."check:i18n-keysconfirms the inlinedefaultValueequals theenvalue.content/docs/guide/data-source.mdgains one sentence after theelement:numberparagraph describing the notice. The docs grep overcontent/docs/**(theelement:numberrow and paragraphs indata-source.md) found no sentence this makes false..changeset/10951-element-number-no-object-state.md,'@object-ui/components': patchand'@object-ui/i18n': patch(the second because the ten packs are@object-ui/i18nsource).(b) measured: the manifest cannot say "
objectordataSource.object" todayRouted by the seat: (b) is the maintainer's decision, on objectui#10991 (it recommends A: keep this notice and add no new gate). This PR closes objectui#10951 with (a), the runtime half; the authoring-time half rides that decision card, which also decides objectui#10508's calendar and gantt trade and
element:record_picker.Nothing was registered and nothing was invented. What was read on
origin/maindeca847a8a:ManifestInput(packages/sdui-parser/src/types.ts) carriesname,type,of,required?: boolean,enum,binding,description.ManifestComponentcarriestype,namespace,inputs,isContainer,tier. There is no one-of, any-of, requiredAny or conditional-required form, and no component-level validator slot. The serializer forwards exactly that fixed per-input list (the listcomponent-input-retired-constraint-keys.test.tsquotes: "name,type,of,required,enum,binding,description").validateTree's only presence rule is per input and unconditional:if (input.required && !(input.name in node))drawsmissing-required-prop.validateTreeare not registration hooks.checkKanbanQuickAddfires onQUICK_ADD_HOST_TYPES = new Set(['object-kanban'])andcheckDashboardWidgetOptionsonDASHBOARD_WIDGET_HOST_TYPES(dashboard,dashboard-grid). Both sets are hard-coded in the parser; no registration declares them andsdui.manifest.jsondoes not carry them. A third arm forelement:numberwould be a NEW parser rule, and the objectstack side runs a lockstep port ofpackages/sdui-parser/src(objectstackpackages/sdui-parser/objectui-lockstep.jsonrecords the file list and thediagnosticCodesset), so it is a cross-repo contract, not a registration.objectNamelostrequired: truebecause the record source is one ofdata/staticData/objectName, and the description states the rule instead.element:numberin PR objectui#10944 is the same shape; one answer covers both.⇒ (b) goes back to the seat as an open question, with a four-axis analysis in the
os-dev-reportcomment on the card.Pins: red on the base, green on the head
New files (both under
packages/components/src/renderers/basic/__tests__/):elementNumber.noObjectState-10951.test.tsx(no provider, EnglishdefaultValue):dataSource: { object: '' })elementNumber.noObjectStateLocale-10951.test.tsx(its own file, becausecreateI18ninstalls a module-global instance):deca847a8a(the two test files copied in, nothing else)pnpm exec vitest run --maxWorkers=2over both filesTests 4 failed | 5 passed (9): the 4 PIN rows fail withUnable to find an element by: [data-testid="element-number-no-object"]; the 5 CONTROL rows passaffc832f76Tests 9 passed (9)Ablation, with each control lit
Every leg mutated the COMMITTED
elements.tsxthrough objectstack'sscripts/ablation-replace.mjs(the anchor must hit once, the write is verified on disk by anchor count and blob hash, and the restore is proven by blob equal to the HEAD blob plus an emptygit diff HEAD). The test imports the renderer through its relativesrcpath, so nodiststep sits between the mutation and the run.false(both files run)Tests 4 failed | 5 passed (9)!props.objectprops.aggregatestatus === 'absent'clauseprops.objectalone, drawn ahead of the two panelsM5's first attempt was a no-op.
ablation-replacerefused it because the replacement re-contained its anchor ("the anchor count moved 1 to 1", paraphrased), and nothing ran. It was re-run with a non-overlapping anchor, and that run is the one reported above. Every leg ended with "ok restored: blob == HEAD (6c2e326522b8)".Gates (all on head
affc832f76; heavy runs underos-verify-lock)pnpm turbo run build --filter="@object-ui/components^..." --concurrency=2→Tasks: 7 successful, 7 total.pnpm --filter @object-ui/components --filter @object-ui/i18n run type-check→ bothtype-check: Done, lockVERDICT command-exit 0.tsc -p tsconfig.test.json --listFilesOnlylists both new test files andelements.tsx.pnpm exec vitest run --maxWorkers=3 packages/components/ packages/i18n/→Test Files 401 passed | 1 skipped (402),Tests 4380 passed | 37 skipped (4417), includingall-locales-key-parityanden-zh-key-parity.element:number/elements.tsx(git grepover*.test.*, 24 files inapps/console,app-shell,cli,core,data-objectstack,plugin-dashboard,react,types,scripts), plus the fivescripts/__tests__suites that read the locale packs whole →Test Files 29 passed (29),Tests 1931 passed (1931).vite buildofapps/console, base and head built the same way in separate worktrees):✅ Console eager closure is 3104.0 KB gzipped across 330 of 2446 chunks (budget: 3104.5 KB, headroom: 0.5 KB). The base read 3103.9 KB (headroom 0.6 KB) on this box. Delta:eagerGzipBytes3178359 → 3178445 = +86 bytes gzipped (ui-components+78,i18n-locale-en+34, the rest hash churn). 563 bytes of headroom remain.check:eager-locale-cataloguesexit 0 (enstill the only eager catalogue);check:sdui-registration-pinsexit 0.check-changeset-presenceexit 0 ("13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)");check-changeset-no-majorexit 0;check:pending-changeset-literalsexit 0;check:changeset-claimsexit 0, report-only. It names three pending changesets (6661, 6726, 6783) that mentionen.tsorelements.tsx, and all three paragraphs were re-read and are still true.check:new-line-citations→VERDICT new-cross-file-line-citations: 0 new citation(s);check:control-bytesOK;docs:check-links"Links are valid across 17 scan roots.";check:doc-typesexit 0;check:element-data-source-declarationOK.check:i18n-keysexit 0;check:i18n-driftexit 0 ("1 key(s) added");check-i18n-dead-keysdoes not listelement.number.noObject.eslint --no-inline-config --format jsonover the 13 changed.ts/.tsxfiles → 13 files, 0 errors, 24 warnings. All 24 are inelements.tsxand the base file carries the same 24.eslint.config.jssets noparserOptions.project(type-aware linting is off), so this diff cannot move a verdict on any file it did not touch.git merge-tree --write-treeagainst a freshorigin/main(42687baf20) → exit 0, no conflicts.Acceptance notes
vite buildofapps/consoleonly, src aliases). The +86 byte delta is base against head on the same box.ElementDataSourceErrorPanel/ElementDataSourceLoadingPanelin@object-ui/react) andelement:image's "no image source" still render English literals. This PR does not touch them.Part of #10951, so the merge does not close a card in the decision box.Generated by Claude Code