Skip to content

fix(core): the id_field no-declared-twin diagnostic carries the spec's published FIELD_KEY_GUIDANCE reason (objectui#7650) - #11175

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-7650-id-field-guidance
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-7650-id-field-guidance

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #7650
Clause-②: no — a dev-mode diagnostic's text now comes from the spec's published guidance; no accepted document, published type or served value changes. The PR still waits as a draft for the director seat's review.

The one open slice of objectui#7650, id_field, as claimed in comment 5904884226. ⚠️ This stays a DRAFT for the director seat's contract review, per the maintainer's instruction of 2026-09-29 quoted in the dispatch. The dev does not mark it ready and does not enable auto-merge.

Implemented in session https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm (PM seat domain:spec @ objectui, dispatched dev).

What changes

In packages/core/src/utils/reference-keys.ts, warnUnfoldableRetiredKey now appends FIELD_KEY_GUIDANCE.id_field.why to the dev-mode line when the refusal is no-declared-twin and the authored key is exactly id_field. FIELD_KEY_GUIDANCE is imported from @objectstack/spec/data, next to the FieldSchema import the module already had.

  • ⛔ No local copy of the spec's prose, and no optional read with a fallback. These are the two refusals the module comment recorded, and neither one moves. The read is the bare expression FIELD_KEY_GUIDANCE.id_field.why, with no ?. and no ??. It sits after the isDev() and memo checks, so production never evaluates it. On a spec without the row it throws in dev, before the memo is taken, instead of warning with less.
  • ⛔ The fold does not change. canonicalizeRetiredFieldKeys is not touched, id_field is never folded, and no idField read is added. lintAuthoredRecordKeys is not called and no near match is offered. The appended text is the contract's own sentence about the key. A typo (sortible) gets neither a suggestion nor the id_field reason.
  • Only the exact spelling gets the reason. The spec keys that row in snake_case on purpose: a why row matches only the exact authored spelling, with no alias probe. The diagnostic follows the same rule.
  • Module comment: the paragraph that called the slice 「NOT TAKEN」 now says what the code does, under the heading id_field: never folded, and the diagnostic carries the spec's own reason. The no-declared-twin bullet in the warnUnfoldableRetiredKey docblock gets one matching sentence.
  • Changeset .changeset/7650-id-field-published-guidance.md bumps @object-ui/core as a patch. There is no floor raise (see premise 2).

Premises, re-measured on origin/main 0ffc423b1 before writing

  1. Installed row. packages/core/node_modules/@objectstack/spec/package.json reads 17.5.0. FIELD_KEY_GUIDANCE is exported from the @objectstack/spec/data entry and typed as a readonly record of { to?: string; why?: string }. Its id_field row has one key, why: a why and no to. Lit control in the same read: the startingNumber row is present with a why. Dark control: in the npm-packed 17.4.0 tarball, dist/data/index.mjs has 0 hits for id_field and 2 for startingNumber. npm lists no version between 17.4.0 and 17.5.0.
  2. Consumer floor. @object-ui/core already declares "@objectstack/spec": "^17.5.0" (raised by PR objectui#11086). No raise was needed, so the lockfile is unchanged. check:spec-floors, run after building core, reports no finding for @object-ui/core and 0 floor-too-low findings overall. The gate's exit 1 comes from 18 no-artifact findings, all for packages not built in this worktree. That is a missing prerequisite, so those 18 are NOT MEASURED; it is not a red.
  3. Before reading, taken through the real exported normalizeSchemaReferenceKeys (source module, node --experimental-strip-types). The id_field line went straight from "...and this path deliberately does not guess a near match." to "The key and its value are LEFT on the def exactly as served...", with no spec reason. After the change, the same line has "@objectstack/spec's FIELD_KEY_GUIDANCE gives the reason: " plus the installed row's why between those two sentences. The sortible and startingNumber lines from the same probe are unchanged.
  4. ⚠️ This premise is partly false. ExpressionProvider.tsx and RecordFormPage.tsx mention FIELD_KEY_GUIDANCE only in comments. Nothing in the tree imports it: git grep for an import of the symbol finds 0 hits, against a lit control of 192 files that import from @objectstack/spec/data. So the import follows this module's own FieldSchema import from the same subpath.

Tests

The new describe the id_field diagnostic carries the PUBLISHED spec reason (objectui#7650) is in reference-keys.retiredDialect-7650.test.ts. Every read of the row happens at test time and is unconditional. The test holds no copy of the prose.

  • the installed row is a retirement — a why, and no to
  • the no-declared-twin line for id_field carries that why, and the key is still not folded
  • the reason is read off the spec module when the line is built — not from a copy: substitutes the row through vi.doMock with importOriginal
  • a spec WITHOUT the row fails loudly in dev instead of warning with less, and production never reads it: removes the row through vi.doMock; the production leg is the control
  • a TYPO still gets no suggestion — and not the id_field reason either

(The backticks around the key names inside those test names are dropped here.) Two sentences in the same file were corrected because this change made them false: the header bullet that said the row "is in NO published version", and the title of the id_field negative pin, which said "the slice is blocked on a spec release". That pin is now titled does NOT fold id_field — no declared successor.

Ablations: each from committed HEAD 21306be7b, with the direction predicted before the run

Each leg ran through ablation-replace.mjs. The anchor must hit exactly once, and the tool checks the on-disk anchor and replacement counts and the blob change. Each leg ran under a trap restore on EXIT INT TERM and was restored by state: the blob is back to the HEAD blob 8347e30f9e83, and git diff HEAD is empty. No build was needed: the tests import ../reference-keys by relative path (source), and the mocked module is the unchanged spec dist.

leg mutation predicted observed (4 reference-keys files, 77 tests)
M1 drop the reason from the line: ${detail}${published} becomes ${detail} 2 red: the "carries that why" pin and the substitution pin 2 failed / 75 passed, exactly those two
M2 optional read with a local fallback: ?.why ?? 'LOCAL FALLBACK SENTENCE' 1 red: the row-absent pin 1 failed / 76 passed, exactly that pin
M3 local copy: the read replaced by the installed sentence as a literal 2 red: the substitution pin and the row-absent pin; the "carries that why" pin stays GREEN, because a copy satisfies it 2 failed / 75 passed, exactly those two
M4 the reason widened to every no-declared-twin key 1 red: the typo pin 1 failed / 76 passed, exactly that pin

⚠️ The first M4 attempt was a no-op: the tool refused it before running anything, because the replacement was a substring of the anchor and so its count could not rise. It is not a measurement. M4 in the table is the re-run with a replacement that is not inside the anchor.

Gates at HEAD 21306be7b (exit codes captured before any pipe)

  • pnpm --filter @object-ui/core type-check, after pnpm --filter "@object-ui/core^..." build: exit 0. The test file is part of the tsconfig.test.json program (--listFiles: 1 hit).
  • pnpm exec vitest run --maxWorkers=2 packages/core/: Test Files 190 passed (190), Tests 3737 passed | 27 skipped (3764), VERDICT command-exit 0.
  • The 21 test files outside core that import the normalizer or mention id_field: Test Files 21 passed (21), Tests 192 passed (192).
  • pnpm --filter @object-ui/core lint: exit 0 with 0 errors (the 568 warnings were already there). The two touched files have 0 errors and 0 warnings.
  • pnpm --filter @object-ui/core build: exit 0, and the dist completeness check passes.
  • Exit 0: check-changeset-presence, check-changeset-claims, check-changeset-fixed, check-changeset-no-major, check-changeset-overwrite, check:pending-changeset-literals, check:control-bytes, check:new-line-citations (0 new citations), check:spec-symbols, check:installed-pin-claims, check-vi-mock-override-shape, check-vi-mock-specifiers, check-vi-mock-inherit, check-test-path-roots, check:phantom-deps, check:esm-specifiers (specifier leg), check:unreferenced-sources, check:handler-key-reads, check:designer-field-key-parity, check:self-import, check:unused-deps.
  • check-governed-queue-guard.mjs --test on the three paths: NOT GOVERNED.
  • check:spec-floors: see premise 2. Clean for core; NOT MEASURED for the 18 unbuilt packages.
  • Not run locally because CI owns them: repo-wide pnpm lint, the pnpm test shards and the full type-check. The lockfile gates do not apply, because the lockfile is unchanged.

Acceptance notes

  1. ⚠️ Finding, not fixed here. For id_field (and for title_format), the diagnostic's shared closing clause is not true today. It reads: "the consumers read only the spelling @objectstack/spec's FieldSchema declares, so this value reaches no reader". There are production reads of both snake spellings. In packages/plugin-charts/src/ObjectChart.tsx, the expression fieldDef.id_field || 'id' reads it, and its own census comment traces that def to getObjectSchema. In packages/app-shell/src/utils/resolveActionParams.ts, idField: field.id_field and titleFormat: field.title_format read them. The clause came with objectui#8938 and this PR does not change it, but it now sits on the same line as the spec's reason. The diagnostic text has not been released yet: the changeset 8938-retired-dialect-measured-surface.md is still pending, and @object-ui/core@17.6.0 on npm contains none of that text. So the clause would ship as-is in the next release. Fixing it means deciding who the readers are, or retiring those two reads, which the spec's reason argues for. That is a judgement for the reviewing seat, so it is reported to the seat and not filed by this dev.
  2. Observation, not filed. Other FIELD_KEY_GUIDANCE rows that carry a why (for example startingNumber) also land in the no-declared-twin arm, and they get no reason. They are outside this slice under both the claim's file surface and the no-optional-read rule. Generalising would need its own ruling on what the line says when a row is absent.

维护者速读(草稿)

  • 改了什么:对象里存着旧键 id_field 时,开发模式下的那条诊断现在会带上 spec 官方写的理由(FIELD_KEY_GUIDANCE.id_field.why,直接读已安装的 spec,本仓不存副本)。
  • 没改什么:id_field 仍然不会被折叠成别的键;生产环境行为、服务出去的数据、公开类型都不变。
  • 风险:spec 若缺了这一行,开发模式直接报错而不是悄悄少说一句(这是卡上两条拒绝的要求);依赖下限 ^17.5.0 已经保证有这一行。回滚就是撤这个 PR。
  • 席位意见:(留空,待总监席契约复审)
  • 你要做的:无;等总监席复审。另有一条发现(诊断末句「没有读者」对 id_field 不成立)交席位判断。

Round 2: the closing clause no longer claims that no reader sees the value

In response to the seat's ACCEPT comment 5905375580 on objectui#7650. Head a71744d4f. Only words change. No read, no fold and no id_field reason logic changes, and the one code line that changes is the message string.

  • Why. The line ended on "so this value reaches no reader". That is false where a retired spelling is still read on purpose. resolveActionParam (@object-ui/app-shell) reads idField: field.id_field and titleFormat: field.title_format. objectui#7435 kept those reads, and its pin resolveActionParams.declaredLookupLegs-7435.test.ts calls them "the snake spellings that must keep working". resolveGroupByLabels (@object-ui/plugin-charts) reads fieldDef.id_field || 'id'.
  • Diagnostic. The closing clause is shared by all three refusals. It now reads: "but a consumer that reads only the spellings @objectstack/spec's FieldSchema declares will not see this value."
  • Docblock. In the warnUnfoldableRetiredKey docblock, "a key that does not fold reaches none of them" and "a value nothing reads" now apply only to the consumers of the declared spellings. A new paragraph names the kept readers by symbol (resolveActionParam, resolveGroupByLabels). It says the list is not complete (deriveColumns in @object-ui/plugin-form also reads display_field) and that nothing re-derives a complete list. Two other sentences in the same file made the same claim and are narrowed the same way: the module docblock's "the consumers … read none of it", and the canonical-occupied bullet's "the retired one is inert". The canonical-occupied comment in the 7650 pin file ("reaches no consumer") is narrowed the same way too.
  • Pending changeset 8938-retired-dialect-measured-surface.md. One dated correction note is appended at the end, under the maintainer's ruling 「Allow the appended note (Recommended)」. git diff of that file shows 11 additions and 0 deletions. Its first 39 lines (2491 bytes, frontmatter included) are byte-identical to the base. The note also corrects that file's "the retired value is inert", which makes the same claim for the occupied case. check-changeset-overwrite lists the file as a modified pre-existing changeset (exit 0). The declaration is unchanged: @object-ui/core: minor before and after.
  • This PR's own changeset gets one sentence about the correction.

Pins. Two new tests in the same describe:

  • the id_field line never claims NO reader sees the value — kept snake reads do: the line does not contain "reaches no reader". As a lit control on the same line, it still carries the narrower claim.
  • CONTROL — the scoped claim is kept where it is true, beside the fact it states: an occupied displayField that keeps the producer's value, plus a typo. Both lines carry "will not see this value".

(The backticks in the first test name are dropped here.)

Ablation, from committed HEAD a71744d4f: the old two-line clause was restored through ablation-replace.mjs. The anchor went from 1 hit to 0 and the blob from 5cc832c5b346 to e0eaee263d61. "reaches no reader" counts 1 in the mutated file and 0 at HEAD. The prediction was 2 red, the two new pins. The run gave Tests 2 failed | 77 passed (79), and the two failures were exactly those pins. The file was restored and checked by state: the blob is back to 5cc832c5b346 and git diff HEAD is empty. A trap on EXIT INT TERM guarded the restore.

Gates at a71744d4f (exit codes captured before any pipe):

  • pnpm --filter "@object-ui/core^..." build then pnpm --filter @object-ui/core type-check: 0. pnpm --filter @object-ui/core lint: 0, with 0 errors. The 568 warnings were already there, and the two touched source files have 0 errors and 0 warnings.
  • The four reference-keys files: Test Files 4 passed (4), Tests 79 passed (79). packages/core/: Test Files 190 passed (190), Tests 3739 passed | 27 skipped (3766). The 21 outside files: Test Files 21 passed (21), Tests 192 passed (192).
  • Exit 0: check-changeset-presence, check-changeset-claims, check-changeset-fixed, check-changeset-no-major, check-changeset-overwrite (it reports the appended 8938 changeset as its case 2, a deliberate correction), check:pending-changeset-literals, check:control-bytes, check:new-line-citations (0 new). check-governed-queue-guard.mjs --test on the four paths: NOT GOVERNED.

Noted, not changed. A test title in reference-keys.measuredSurface-8938.test.ts, speaks up when the canonical key is OCCUPIED — the retired value is inert, and that was silent, makes the same "inert" claim. That file is outside the claim's file surface, so it is left for the seat.

The Clause-②: no declaration above still holds, because only text changes.


Generated by Claude Code

…s published FIELD_KEY_GUIDANCE reason (objectui#7650)

The leave arm's diagnostic for a stored `id_field` now appends
`FIELD_KEY_GUIDANCE.id_field.why`, read unconditionally off the installed
`@objectstack/spec/data` on the dev-only emitting path. No local copy of the
prose and no optional read with a fallback (both refused on the card): a spec
without the row throws in dev instead of warning with less, and production
never reads it. The fold is unchanged: `id_field` is never folded, no
`idField` read is added, and a typo gets no suggestion.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3569.8 KB 3607.4 KB
Main entry chunk (gzip) 149.5 KB 350 KB
Entry file index-C_2saAHA.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 559.46KB 134.18KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.29KB 63.03KB
fields (index.js) 261.19KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 234.78KB 62.36KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 229.83KB 63.15KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 19.82KB 7.15KB
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

…ees the value (objectui#7650)

Round 2 text correction, words only. The no-declared-twin / occupied /
ambiguous line closed on "so this value reaches no reader", which is false
where a retired spelling is still read on purpose (resolveActionParam reads
id_field and title_format, kept by objectui#7435; resolveGroupByLabels reads
id_field). It now says a consumer that reads only the spellings FieldSchema
declares will not see the value. The module docblock names those kept
readers; the pending 8938 changeset gets one appended, dated correction note
(no existing line edited); this PR's own changeset gets one sentence. No
read, no fold and no id_field reason logic changes.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3569.9 KB 3607.4 KB
Main entry chunk (gzip) 149.5 KB 350 KB
Entry file index-Dc5c4j4W.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 559.46KB 134.18KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.28KB 63.04KB
fields (index.js) 261.19KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 234.78KB 62.36KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 229.83KB 63.15KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 19.82KB 7.15KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a71744d4fcde0691bd32a22bf0776f338a3fad63
Local-runs: none

Director seat's at-tier review (objectstack#12708, session_01AsCNgFBs8HCjwhyHQsFbx3) — the review this PR was drafted for. Inputs: card objectui#7650 (the takeover 5893375409, the unlock 5904085841, the claim 5904884226 with its two refusals, the os-dev-reports 5905337312 / 5905684555 and the seat's two ACCEPTs 5905375580 / 5905704721, read as claims to test); PR #11175 (body, 4 files, +232/−39, net diff against objectui main 0ffc423b13, which is also the merge base); the module at origin/main and at the head; objectstack's spec at the @objectstack/spec@17.4.0 and @objectstack/spec@17.5.0 tags (packages/spec/src/data/authoring-key-lint.ts, data/index.ts); the head's check-runs. Nothing built, run or re-run. merge-tree against main: clean. Check-runs on the head, read 2026-09-30T06:5xZ: 43 runs, 0 failure, 0 in progress; the head's merge base already carries 17.5.0.

① Derived judgments

  • The read — RIGHT, and exactly the shape the card's two refusals demand. packages/core/src/utils/reference-keys.ts imports FIELD_KEY_GUIDANCE from @objectstack/spec/data beside the FieldSchema import the module already had, and warnUnfoldableRetiredKey appends FIELD_KEY_GUIDANCE.id_field.why to the dev-mode line only when the refusal is no-declared-twin and the authored key is exactly id_field. The expression is bare — no ?., no ??, no local copy of the prose — and sits after the isDev() and memo checks, so production never evaluates it and a spec without the row throws in dev rather than warning with less (pinned both ways, with the production leg as the control). The fold is untouched: canonicalizeRetiredFieldKeys does not change, id_field is never folded, no idField read is added, no near match is offered (the sortible control pins it).
  • The contract premise — RIGHT at both tags. FIELD_KEY_GUIDANCE is exported from the data entry (data/index.ts:172 → authoring-key-lint.ts) at 17.4.0 and 17.5.0; the id_field row exists only at 17.5.0 (authoring-key-lint.ts:146), with a why and no to — a retirement, as the new pin states. @object-ui/core declares @objectstack/spec ^17.5.0 on main (raised by PR chore(deps): resolve @objectstack/* 17.5.0, and the zod 4.6.5 it requires, in pnpm-lock.yaml (objectui#11073) #11086), so no floor moves and the lockfile is untouched; 17.5.0 is the first published release with the row, so the floor is exact.
  • The corrected clause — RIGHT, and the correction was owed. The closing clause shared by the three refusals said the left-alone value "reaches no reader"; on main resolveActionParam (app-shell) reads id_field / title_format on purpose (objectui#7435's pin names them the snake spellings that must keep working) and resolveGroupByLabels (plugin-charts) reads fieldDef.id_field, so the sentence was false for two keys. It now says a consumer that reads only the spellings FieldSchema declares will not see the value — true for every refusal — and the docblock names the kept readers by symbol as named, not counted. The code path is right because those reads were kept by ruling; only the words move, which is what the seat's answer A ruled for round 2. Retiring those reads is a separate decision nobody took here. RIGHT.
  • Public surface. @object-ui/core: a dev-mode diagnostic string changes; no export, type, accepted document or served value moves. Round 2's only code line is the console.warn string.
  • Statements tested. The changeset 7650-id-field-published-guidance.md is true on main the moment this lands, including its floor sentence. The pending changeset 8938-retired-dialect-measured-surface.md gains one dated correction note, append-only (git diff --numstat 11 / 0 against the base; frontmatter and the original 39 lines untouched) — the maintainer's own answer to the seat, 「Allow the appended note (Recommended)」, recorded in the seat's ACCEPT; the note corrects the same "inert" / "reaches none" claims in that file. Not a head move on someone else's text: it is this family's own note, in the form the maintainer chose.

② Semver level

Clause-②: no — as declared: a dev-mode diagnostic's text, read from the published guidance; no accept set widens or narrows. .changeset/7650-id-field-published-guidance.md: @object-ui/core patch — a fix in a released package, never skip-changeset. One package named, the one that publishes; the pending-changeset note publishes with its own family's release. RIGHT.

③ Boundary flags

  • The dev's open question (the false "reaches no reader" clause): answered by the seat as A, a text correction in this PR — RIGHT, as judged in ①.
  • Premise 4 partly false (nothing in the tree imported FIELD_KEY_GUIDANCE; two files mention it in comments): answered — the import follows the module's own FieldSchema import from the same subpath; no consequence.
  • check:spec-floors exit 1 locally (18 no-artifact findings for unbuilt packages): NOT MEASURED locally, a missing prerequisite; the head's CI is green.
  • Noted by the seat, not filed, and not this PR's: a test title in reference-keys.measuredSurface-8938.test.ts still says "the retired value is inert" (rides the next change to that file); LookupField reads four snake spellings before their camel twins (a producer-order question for its own card).
  • Fixes #7650: id_field was the card's last open slice; the card closes on landing. RIGHT.

Implemented-by: claude/issue-7650-id-field-guidance
Reviewed-by: session_01AsCNgFBs8HCjwhyHQsFbx3

VERDICT: PASS

State: needs:contract-review comes off PR #11175 in the same stroke (this PASS at this head); the PR stays a draft until its seat lands it through the queue.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 07:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 1e215c4 Sep 30, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-7650-id-field-guidance branch September 30, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants