Repository navigation
fix(plugin-timeline): a lookup-typed title or description renders its display name instead of crashing the timeline (objectui#10530) - #10593
Conversation
… display name instead of crashing the timeline (objectui#10530) ObjectTimeline's own object fetch expands every declared relation, so a lookup-typed titleField or descriptionField arrived as an expanded record, and the item mapping copied it raw into a React child position. The mapping now derives both slots once through @object-ui/core's recordDisplayValueAt, the resolver ObjectMap uses for the same slots. Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
…totype.at (objectui#10530) The package's test tsconfig targets a lib without Array.prototype.at, so type-check refused the two reach-path arms. Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN Co-authored-by: Claude <noreply@anthropic.com>
✅ 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:
① Derived judgmentsMechanism — as prescribed. Resolver semantics ( Behaviour that moved. All four are consequences of the ruled resolver meeting the renderer's UNCHANGED Reach-path pin. H4 / H5 and the two inferences, confirmed at source. H4: ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10530
Clause-②: no
What changed
ObjectTimeline's item mapping copied the raw field values onto each item (title: item[titleField],description: item[descField]), andTimelineRendererputs both in JSX as React children. The component's own object fetch expands every declared relation throughbuildExpandFields(objectDef?.fields), so a lookup-typedtitleFieldordescriptionFieldarrives as{ id, name }and React throwsObjects are not valid as a React childfor the whole rail.The mapping now derives both slots once, through
@object-ui/core'srecordDisplayValueAt, the same helperObjectMaphas read its description through since PR objectui#10525. No new resolver, andpackages/core/src/utils/record-title.tsis not touched.Files, and nothing else:
packages/plugin-timeline/src/ObjectTimeline.tsx: therecordDisplayValueAtimport and the two mapping lines (plus the comment above them).packages/plugin-timeline/src/ObjectTimeline.lookupDisplay-10530.test.tsx: the new pin, which mirrorsObjectMap.descriptionDisplay-10456.test.tsx..changeset/10530-timeline-lookup-display.md:'@object-ui/plugin-timeline': patch.Mechanism hypotheses, measured
Each row was measured with a one-shot probe through the real
ObjectTimelineand the realTimelineRenderer, on base6ea68e63eand again on head. The probe was then deleted and never committed.$expand["account"], then the render threwObjects are not valid as a React child (found: object with keys {id, name})fordescriptionField: 'account'and again fortimeline.titleField: 'account'.ObjectMapprecedenth3/ptext on base and head. Partly falsified for a boolean: on basetruedrew an empty title and description element andfalsedrew nothing, so neither rendered text today. On head they readtrue/false, per the resolver's rule. Other measured moves: a0was painted as a bare character beside the line on base and now sits inside it; a whitespace-only value drew an empty-looking line on base and draws none now; a padded string is now trimmed.nullandundefinedrender nothing on both.schema.itemsgo through the same mappingif (schema.items) return schema.items;), so no mapping runs and no fetch expands anything there. An authored item whosetitleis an object still crashes on head. That path is outside this fix and is listed below.groupByFieldreaches JSX as an object[object Object], on base and on head. It is not the same mechanism, so it is not fixed here and is listed below.Red on base, green on head
The pin file was run on base in a detached compare worktree at
6ea68e63e, with bloba46e0f02d(the same blob as HEAD's), and on the head worktree ata37ee1039.$expandcontainsaccount), and the rail showsAcmethe rail must render, not throw: expected 'Objects are not valid as a React chil…' to be nulltimeline.titleFieldObjects are not valid as a React child (found: object with keys {id})expected [ '' ] to deeply equal [ '0', 'false', 'true' ]Base:
Tests 4 failed | 2 passed (6). Head:Tests 6 passed (6).The server double answers the bare foreign key unless the query asks for
accountin$expand. SoAcmecan reach the rail only through the component's own expansion.Gates, run at HEAD
a37ee1039pnpm exec vitest run packages/plugin-timeline/:Test Files 29 passed (29),Tests 338 passed (338).pnpm --filter @object-ui/plugin-timeline run type-check(bothtsc --noEmitandtsc -p tsconfig.test.json): exit 0. The dependency closure was built first. The new test file is in that program: its first run refusedArray.prototype.at, which the second commit replaces.pnpm --filter @object-ui/plugin-timeline run lint:0 errors.ObjectTimeline.tsxhas 26 warnings on base and 26 on head, and the new test file has 0.node scripts/check-changeset-presence.mjs:2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).check-changeset-no-major,check-changeset-overwrite,check-changeset-fixed,check-changeset-claims,check-pending-changeset-literals: all exit 0.pnpm check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s).pnpm check:control-bytes: OK.check-vi-mock-specifiers,check-vi-mock-inherit,check-vi-mock-override-shape,check-test-path-roots,check-shell-escape-residue,check-unreferenced-sources,check-phantom-dependencies: all exit 0.check-governed-queue-guard.mjs --teston the three paths:NOT GOVERNED.plugin-list,plugin-view,apps/consoleandexamples/schema-catalog(5 files,Tests 633 passed (633)).No doc or README states a lookup title or description behaviour, and no pending changeset does either, so there was nothing to reconcile.
Acceptance notes
These were found on the way. None of them is filed, and none is fixed here.
groupByFieldgroups under[object Object]. This is H5, measured through the real component: records with different accounts merge into one lane, and its header reads[object Object]. The site isObjectTimeline's group-label step, which falls back toString(m.group). Grouping a lookup by display name or by id is a design choice, so this is not a mechanical fix. Not measured at a public door, and no in-repo producer authors a lookupgroupByFieldfor a timeline. Carrier: none.schema.itemswith an object-valuedtitlestill crash. This is H4. It is the author-literal pass-through path: no fetch runs and no lookup is expanded there. Carrier: none.ObjectTimeline'sNavigationOverlaybody printsString(value ?? '—')for every record key, so an expanded lookup would read[object Object]there.ObjectGallery's card title doesString(item[titleField])for an authoredtitleField, which reads the same way for a lookup. Carrier: none.Generated by Claude Code