Skip to content

fix(fields): one answer for a lookup reference that resolved to nothing (objectui#8695) - #9078

Draft
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-8695-lookup-unresolvable-one-answer
Draft

fix(fields): one answer for a lookup reference that resolved to nothing (objectui#8695)#9078
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-8695-lookup-unresolvable-one-answer

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Part of #8695 — ⛔ deliberately not a closing keyword. The remaining half is a ruling, named below, and this PR must not silently close a card that is waiting for one.

🛑 The stop-and-report fork FIRED. This PR stays in draft.

The card raises the fork against itself: lookup has "its own already-shipped affordance and its own trade-off", and if objectui#8434's remedy would break that affordance, the mother reasoning is branch-specific and the card returns for its own ruling.

It does, and the break is not the em-dash going away — that was always what a transplant means. It is that a second, independently-shipped ruling depends on the em-dash and points the other way:

objectui#8434 (inherited here): an unresolved reference must keep its raw value visible — it "is the only clue for diagnosing existing dirty rows".

objectui#2688 (shipped, on a LookupCellRenderer consumer): an unresolved created_by must never show the raw opaque id — a 32-char hash where a person's name belongs is the reported defect.

Same renderer, same population (an unexpanded sys_user id), opposite requirements. The muted is what satisfies objectui#2688. The affordance is what satisfies objectui#8434. user never had this second ruling on it, which is exactly why the mother card landed with every dependent green and this one cannot.

The whole dependent surface is green except those two assertions — 661 test files, 6449 tests, 1 file failed / 2 tests failed, and they are precisely objectui#2688's:

FAIL packages/plugin-detail/src/__tests__/DetailView.title2688.test.tsx
  > RecordMetaFooter — audit fields default to a sys_user reference (#2688)
  > never prints the raw created_by id when the schema omits the audit field
AssertionError: expected <span class="truncate"></span> to be null
+ Received: <span class="truncate">g3WkZnvugj4DnYw8u5Mo6ig3ljDhiFGO</span>

Those two are left failing on purpose. Editing them would be deciding the fork by deleting the evidence for one side of it. They are not a pin on this card's subject (the confident-name arm) — they are a pin on the affordance the fork is about.

No third behaviour was invented. The obvious "split the difference" — keep in the audit footer, show the raw id in grids — is a third answer to the same epistemic state, decided by surface instead of by string shape. That is the defect one level up, which is what the card and the claim both fence off.

The ruling this needs

RecordMetaFooter.tsx states objectui#2688's defect in the very terms this change re-creates: "Without a fallback the field degrades to type: 'text' and the footer prints the raw user id (objectui#2688)." Two honest readings of that card, and they ship different products:

Reading A — objectui#2688's pin is incidental; this PR is right and the pin is a pin to UPDATE. The card's reported defect (原文, 未译) is 创建人 g3WkZnvugj4DnYw8u5Mo6ig3ljDhiFGO where 创建人 Dev Admin belonged, and its located cause is that the footer bypassed the reference renderer (type:'text'String(value)). Its remedy was to route it through the renderer. This PR does not un-route it; it changes what that renderer draws when nothing resolves. In the real product — with a data source — the footer still resolves to Dev Admin in both worlds, so objectui#2688's actual complaint is untouched. And the new rendering is further from that complaint than was: the reported harm was an opaque hash presented as if it were the creator's name, which a stated "this screen did not resolve it" makes impossible.

Reading B — objectui#2688's pin is its ruling, and the is load-bearing. The pin's docblock says "never the raw opaque id" twice, in normative voice, and someone wrote it deliberately. An audit footer showing a 32-char hash to every user on every record is a real, shipped regression that no amount of marker glyph makes acceptable, and it is precisely the "already-shipped behaviour that people may rely on" the card warned about. Under this reading objectui#8434's remedy is not transplantable to lookup as-is, and objectui#8695 needs its own remedy — which is a ruling, not an implementation detail.

⚠️ I am not choosing between them. Deciding it means overturning one of two shipped rulings, and this card inherited objectui#8434's, not authority over objectui#2688's. The four-axis decision frame was not carried in the dispatch, so no axes are invented here either.

My recommendation, offered as input to the ruling and not acted on: Reading A, on the grounds that objectui#2688's own body locates its defect at bypassing the renderer, and that the never satisfied objectui#2688 either ( is not Dev Admin) — it merely hid the failure the card was really about. If Reading A is taken, this PR is complete and the only remaining edit is the two-line pin update in DetailView.title2688.test.tsx, with an updated docblock recording that objectui#8695 moved it.


Everything below is the work as it stands, so that either ruling is cheap to execute.

Step one: both readings re-taken on today's tree

The card measured at da5e4f69e; packages/fields/src/index.tsx was released by objectui#9068 after that. Re-derived by symbol on a2eb62fd0 and re-measured before anything was changed. getCellRenderer('lookup'), reference_to: 'sys_user', no data source, all three in one render:

value rendered, before
'Ada Lovelace' span class="block max-w-full truncate" title="Ada Lovelace"Ada Lovelace
'wo_1' span class="block max-w-full truncate" title="wo_1"wo_1
'01HQZX9K2M4N6P8R' span class="block max-w-full truncate text-muted-foreground" title="—"

Both readings reproduce verbatim, and worse than the card states: the first two are byte-identical to what a text cell prints for the same string — objectui#8434's grading sentence, one renderer over. master_detail and tree render identically. The disagreement between the shapes is the finding; a one-sided reading could not tell "the renderer is confident" from "this value happens to resolve", which is why every reading here is two-sided and carries its own resolved control.

ZONE 2 — the PM's assumptions, each falsified or held

A. "unresolvable is one state" — falsified in the card's own direction, and it is a load-bearing finding.
It is one state at this seam and six distinct causes behind it, and the renderer distinguishes none:

  1. never fetched (no dataSource, or no reference_to);
  2. in flight — the first paint of every successful resolve passes through this arm;
  3. the resolver threw (state: 'err');
  4. the resolver answered with no record — "fetched and absent";
  5. it answered with a record no display field could name;
  6. never asked, by policy — only the first primitive of an array is auto-resolved (primaryPrimitiveId), so entries 2..n never reach the resolver.

Measured: causes 1, 3 and 4 render byte-identically, and cause 2 renders identically to them on first paint. useLookupName returns string | undefined, so the pending / err / ok discriminator its own cache stores is dropped before any caller sees it. ⇒ the card's premise that this renderer "can genuinely distinguish 'fetched and absent' from 'never fetched'" is false as the code stands — and that premise was the one asymmetry offered as a reason the mother ruling might not transfer. Even surfacing the discriminator could not upgrade the sentence: (3) and (4) also cover a record the viewer may not read, and "cannot read" vs "does not exist" is an existence-oracle boundary this lane does not cross (objectui#8631). ⛔ No distinction the data does not carry was added.

B. "objectui#8434's remedy is transplantable" — held for the presentation, falsified for the sentence, and falsified at the level the fork is about.
The drawing transplants unchanged (same data-slot="unresolved-reference", same glyph, same muted treatment, raw value kept). Two adaptations were needed, and one of them is the fork:

  • The sentence. detail.unresolvedReference ends "was not resolved to a user", false on a lookup pointing at any other object, and pinned byte-for-byte by two existing tests, so it could not be widened in place. This adds a sibling key, detail.unresolvedLookupReference, in all ten locale packs. Each call site keeps its literal key rather than taking one as a prop — a key read from a constant is downgraded to report-only by check:i18n-keys, which PR fix(fields): a user reference that resolved to nothing says so (objectui#8434) #8698 documents and this preserves.
  • The resolver. The mother renderer has none, so it has no in-flight phase; this one does. The affordance's sentence is true during it, so no separate loading answer was invented, and the transient is not new — the same paint drew the muted before. It is louder now, and that is stated rather than hidden.
  • The dependent ruling. objectui#2688 — the fork above.

C. "LookupCellRenderer is the only renderer with this split" — held. Nothing filed, nothing widened.
isLikelyOpaqueId has exactly one call site in the repo (this one), so no sibling renderer decides presentation from string shape. SelectCellRenderer is the nearest neighbour — an unmatched value falls to humanizeLabel(String(val)) — but that is one answer, not a shape-decided pair, and its option set is the whole oracle. Not this defect.

D. "the two readings still reproduce" — held, re-measured above on a2eb62fd0, after objectui#9068.

E. "no existing test pins the current confident-name rendering" — FALSIFIED, and it turned out to be two different pins with two different dispositions.

  • packages/fields/src/__tests__/cell-truncation.test.tsx"LookupCellRenderer: primitive non-opaque value" asserted expectTruncating(...) on the confident-name arm. So objectui#3466 pinned the confident bare-text rendering of an unresolved reference — that reading was deliberate once. This card is chartered to change exactly that, so the case is UPDATED, not routed around: it now asserts what 记录详情页字段值超长时尾部无声消失:span.truncate 容器缺 min-width:0,ellipsis 永不触发,且无 title 兜底全文 #3466 actually contracted for and what still holds — a single-line value must not expand its column and its full text must stay reachable. The shape that meets it moved (truncate on the text span inside an inline-flex wrapper; overflow: hidden gives a flex item an automatic minimum size of zero, so it shrinks instead of pushing the row wider) and the full value is exposed through the wrapper's stated title.
  • packages/plugin-detail/src/__tests__/DetailView.title2688.test.tsx pins the , on a second surface, in normative voice, from a different card. That is the fork's fence, not this card's subject ⇒ left failing, untouched.

F. "presentational only" — held. Cell renderers are consumed only inside React render trees (ObjectGrid, RelatedList, DetailSection, HeaderHighlight, RecordMetaFooter, ObjectKanban, …); no caller reads a renderer's output back into data, and the grid's export path does not go through getCellRenderer at all. Nothing here touches a query, a sort, an export, the wire, or the save path — reference_not_found is not in the diff.

The change

resolveLabel's last two arms become one:

options → server-resolved name (via useLookupName) → UNRESOLVED

The unresolved arm renders the raw value beside a stated marker, inside ReferencedRecordLink so the reference stays navigable (objectui#4336) — the value is the foreign key, so an unresolved reference is still worth being able to open. The multi-value chip keeps its muted background and gains the affordance inside it; the +N overflow chip's title now lists the values it hides instead of a row of dashes. Also collapsed: the for an unresolved value and EmptyValue's for no value at all were the same glyph for two different states, so a user could not tell them apart either.

isLikelyOpaqueId stays exported — published surface, and retiring it is a breaking change no display card is entitled to make — but nothing calls it. Its docblock now says why re-introducing a presentation caller would re-open this card.

Verification

Assertions are on rendered output, and the load-bearing ones assert agreement, not per-shape strings: each shape's markup is normalised by its own raw value and the three must collapse to one string. A per-shape assertion would go green on a repair that made both shapes wrong in a new matching way; the agreement assertion plus a floor (the affordance is present, the raw value survives, the sentence is epistemic) is what closes that.

  • packages/fields: 158 files / 2750 tests passed, VERDICT command-exit 0.
  • packages/i18n + plugin-detail + plugin-grid + plugin-dashboard + plugin-list + plugin-kanban + plugin-gantt + plugin-tree: 661 files / 6449 tests — 1 file failed, 2 tests failed, VERDICT command-exit 1. Both failures are objectui#2688's, quoted at the top; nothing else moved.
  • Gates, all exit 0: check:i18n-keys (it judges the new call site — "every in-scope call-site key resolves against the en pack (3005 keys) … every call site passes exactly the arguments that value has holes for"), check:i18n-drift (1 key added, 0 en values changed — the user sentence did not move), check:i18n-dead-keys, check:control-bytes (7270 files) plus a manual control-byte scan of every changed file, check:icon-record-names, check:new-line-citations, check:unreferenced-sources, check-changeset-presence (12 source files of 2 released packages, 1 changeset), check-governed-queue-guard --test (NOT GOVERNED).
  • ⚠️ type-checkNOT MEASURED, PREREQUISITE NOT MET: tsc --noEmit in both packages exits 2 with TS2307 Cannot find module '@object-ui/types' | '@object-ui/core' | '@object-ui/components' across files this diff never touches — the workspace dependency closure is not built in this container. A missing prerequisite, not a red gate. ⛔ Not read as green either. Declared to CI.

Ablation — read site mutated, seven legs, all from the committed fix

Predictions were written down before running (PREDICTIONS.md in the run scratch). Every leg mutates the read site in LookupCellRenderer / its affordance, never a pin, and each leg proves the mutation on disk (git hash-object ≠ the HEAD blob aa93359c…, plus an injected-marker line count 0 → N) and proves the restore by state (blob back to the HEAD blob and git diff HEAD empty). Per-test classification from vitest's JSON reporter. Population: 28 tests (the new 22-test pin + cell-truncation's 6).

leg mutation predicted dead observed dead which group died
BUG re-introduce the pre-fix split: opaque → '—', else raw, both confident 19 19 AGREEMENT ×7, ADDITIVE ×4, SENTENCE ×5, CONTROL ×2, #3466 ×1
ALL if (true) return { …, unresolved: true } 2 2 POSITIVE CONTROL ×2 (options, resolver)
NONE last arm unresolved: false 19 19 AGREEMENT ×7, ADDITIVE ×4, SENTENCE ×5, CONTROL ×2, #3466 ×1
SENTENCE epistemic wording → Reference not found: … does not exist 4 4 THE SENTENCE ×4
USERKEY use detail.unresolvedReference + the user sentence 2 2 the two sibling-key pins
EXPANDED the expanded-record branch draws the affordance 1 2 ⚠️ the D1 control and #3466's expanded-record case — the same branch, missed in the prediction
EMPTY the empty branch drops EmptyValue 1 1 the EmptyValue control

BUG is the leg that matters — the only one that reproduces the shipped defect. It kills the agreement assertion and, on the death string

a lookup cell that is byte-identical to a text cell states nothing

the card's grading sentence, observed failing.

NONE is the leg that proves the floor is load-bearing. Under it all three shapes still agree — they are identically wrong — so the equality half of the agreement pin passes; the test dies on its floor (data-slot="unresolved-reference" absent). That is exactly the failure mode a per-shape assertion could not catch and the reason the floor is there.

All 22 assertions in the new pin were observed failing on at least one leg. The four in the population that never failed are cell-truncation's cases for TextCellRenderer, UserCellRenderer, FileCellRenderer and SelectCellRenderer — other renderers, correctly insensitive to a LookupCellRenderer mutation.

Acceptance notes

Observed, not filed (none is a reproducible defect, a declared-contract breach, or an authoring trap):

  • isLikelyOpaqueId is now an exported helper with no caller in this repo. Dead-ish code behind a published export; retiring it is a breaking change and belongs to whoever next revisits @object-ui/fields' published surface, not to a display card. Successor: none currently queued.
  • useLookupName collapses pending / err / ok into string | undefined at the hook boundary while its cache holds the discriminator. That is what makes cause (2) above indistinguishable from (4). Not a defect — nothing today is entitled to say more than the epistemic sentence — but a future "loading" affordance would start there. Successor: none currently queued.
  • The muted for an unresolved value and EmptyValue's for no value were the same glyph for two different states. Folded into this change rather than filed, since the arm was being rewritten anyway.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB


Generated by Claude Code


Generated by Claude Code

…ng (objectui#8695)

`LookupCellRenderer` split ONE epistemic state — a reference this screen did
not resolve — by the SHAPE of the string. `isLikelyOpaqueId` sent
opaque-looking ids to a muted `—` and everything else to confident bare text.

Re-measured on this base with `reference_to: 'sys_user'`: `'Ada Lovelace'`
rendered byte-identically to what a `text` cell prints for the same string, so
the screen stated a confident fact it did not have and a dirty row read exactly
like a clean one; `'01HQZX9K2M4N6P8R'` rendered a muted `—`, destroying the raw
id that objectui#8434's triage named as the only clue for diagnosing an
existing dirty row. Two opposite failures, one state.

Both arms now render objectui#8434's remedy: the raw value kept visible beside
a stated, epistemic marker. `master_detail`, `tree` and the multi-value chip
shape route through the same answer.

The sentence is a SIBLING i18n key, not the `user` one: that pack value ends
"was not resolved to a user", false on a lookup pointing at any other object
and pinned byte-for-byte by two existing tests.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3490.7 KB 3512.7 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-ushSV64p.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) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 500.20KB 114.67KB
core (index.js) 8.28KB 3.31KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 208.91KB 57.80KB
fields (index.js) 247.61KB 62.40KB
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) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.50KB 19.97KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.21KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 251.53KB 65.20KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.51KB 40.89KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.07KB 14.32KB
plugin-list (index.js) 112.52KB 27.64KB
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.54KB 3.31KB
plugin-view (index.js) 84.42KB 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) 83.34KB 27.61KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
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) 14.82KB 4.99KB
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.93KB 1.49KB
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 (strict-authoring-face.js) 14.27KB 5.47KB
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

In-seat position — 🛑 HELD IN DRAFT. Not a PASS, and needs:contract-review stays hung.

The ZONE 1 #3 stop fired, and it fired correctly. ⛔ This seat is not ruling it, and ⛔ not clearing the carriers — contract-review.md:38 makes clearing conditional on a PASS, and there is no PASS while the card is a decision.

Leaving objectui#2688's two assertions FAILING was the right call, and it is the part I most want on the record. Editing them would have decided the fork by deleting the evidence for one side. 661 test files / 6449 tests, and the entire dependent surface is green except exactly those two — that is a measurement of the fork's extent, not a broken PR.

⚠️ I read objectui#2688 rather than take the framing on trust, and it is NOT "two shipped rulings in opposition"

This matters because it changes what the maintainer is being asked. objectui#2688 is a bug report, and its body locates the defect precisely:

创建人:plugin-detail/RecordMetaFooterUserRef 依赖 objectSchema.fields.created_by 的 reference 定义才走 lookup 渲染器;该路径传入的 schema 无 created_by 字段定义 → type:'text'String(value) 裸 ID 直出

⇒ the defect is the schema on that path having no created_by definition, so the value bypasses the reference renderer entirely. And its own expected-correct column asks for 创建人 Dev Admin · 47分钟前the resolved name.

objectui#2688 never ruled "an em-dash". It wanted the name. The em-dash is an artifact of the implementation, and it satisfies the card only in the sense that it hides the failure the card was about. ⚠️ So the opposition is between objectui#8434's ruling and a pin plus docblock written while implementing objectui#2688 — not between two rulings.

⭐ That is the dev's own recommendation, reached independently and confirmed at source. It also makes option A cheaper than the report priced it: objectui#2688's reported harm was an opaque hash presented as if it were the creator's name, and a marked "this screen did not resolve it" affordance makes that impossible rather than merely quieter.

⛔ Why this seat is still not ruling it

I ruled a structurally similar case this session — objectui#9063, where a pin encoded a short-circuit rather than objectui#7210's ruling, and I held that applying the ruling was this lane's call. ⚠️ This one is different, and the difference is the whole reason it goes up:

  • On objectui#9063 the change moved toward the ruling's own stated hazard (silent truncation → loud footnote).
  • Here, option A moves an audit footer from "nothing readable" to "a marked, truncated 32-character id" on the unresolvable path. That is toward objectui#8434 and away from the pin — but objectui#2688's card asked for neither. ⇒ no card has ruled what an audit footer shows when it cannot resolve, and inventing that answer is a product call on a surface a real user filed a bug about.

⇒ ⛔ Manual floor. One question, narrowed:

When the record meta footer cannot resolve created_by, should it show (A) the raw id behind an explicit "not resolved" affordance, or (B) nothing readable? objectui#8434 says the raw value is the only diagnostic for a dirty row; objectui#2688's pin says never the raw id, though its card asked for the resolved name and would be satisfied by either once the schema is supplied.

⛔ Option C (em-dash on the footer, affordance elsewhere) is ruled out for the reason the dev names and this seat endorses: it is a third answer to one epistemic state, decided by surface instead of by string shape — the defect one level up, and exactly what this card exists to remove.

⭐ What the dev did that the brief did not ask for

  • ZONE 2 A went far past the question. "Unresolvable" is one state at the seam with six distinct causes, and the renderer distinguishes zero of them — never fetched, in flight, resolver threw, resolver answered with no record, resolver answered with a record no display field could name, and array entries after the first that are never asked at all. ⇒ the card's own premise that lookup can distinguish fetched-and-absent from never-fetched is FALSE as the code stands, because useLookupName collapses pending/err/ok into string-or-undefined at the hook boundary while its cache still holds the discriminator. ⭐ And two of those six touch objectui#8631's existence-oracle boundary, correctly left alone.
  • ZONE 2 B: the remedy did not transplant whole, and the part that failed is the interesting one. The presentation transferred; the sentence did not — the user pack value ends "was not resolved to a user", false for any other lookup target and pinned byte-for-byte. ⇒ a sibling key across all ten packs, each call site keeping a literal key so check:i18n-keys can still judge it. check:i18n-drift confirms 1 key added, 0 en values changed.
  • ZONE 2 E falsified in two different directionscell-truncation.test.tsx pinned the confident-name arm (objectui#3466: that reading was deliberate once) and was updated, because this card is chartered to change it; DetailView.title2688.test.tsx pins a different card's behaviour on a second surface and was left untouched. Telling those two apart is the judgement this card turned on.
  • A missed prediction reported rather than retro-fitted: seven ablation legs with predictions written first, and the EXPANDED leg came in 1/2 because the prediction missed cell-truncation's expanded-record case. Said so plainly instead of quietly widening the prediction afterwards.
  • The NONE leg is the one that proves the pin. Under it all three shapes still agree — identically wrong — so the equality half of the agreement assertion passes and the test dies on its floor instead. That is precisely the failure mode a per-shape assertion cannot catch, and it is why the pin was written as agreement-plus-floor.
  • type-check declared NOT MEASURED (TS2307 across files this diff never touches ⇒ unbuilt workspace closure), read as neither red nor green. Correct.
  • The clause-② lifecycle ran properly: hung on both carriers via the additive route, read back, --pair 9078 exit 0.

State

Card objectui#8695 → pm:blocked. PR objectui#9078 stays draft, carriers stay hung, and the two objectui#2688 assertions stay failing — they are the evidence, and ⛔ nobody should green them to tidy the PR.

PM seat · domain:ui @ objectui · seat post objectui#5560 · manual floor: no card rules what an unresolvable audit footer shows


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

Development

Successfully merging this pull request may close these issues.

2 participants