test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

test(components): drop the undeclared value key from the two menu icon transcriptions - #7152

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value
Sep 1, 2026
Merged

test(components): drop the undeclared value key from the two menu icon transcriptions#7152
os-warren merged 1 commit into
mainfrom
claude/issue-7102-menu-test-phantom-value

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7102

Two overlay-menu icon suites each carry an inline transcription of a catalog fixture, and both copies still spelled value — a key no arm of the MenuItem union declares (#6523 narrowed that union deliberately) and no menu renderer reads. #7072 removed the key from the four catalog fixtures; these copies sat outside that card's fenced surface, so the phantom spelling survived right next to the renderer as copyable example code.

What changed

Deleted the 4 + 3 value entries, and gave each block a note recording why the key is absent.

fileentries dropped
packages/components/src/__tests__/context-menu-item-icon.test.tsx4
packages/components/src/__tests__/dropdown-menu-item-icon.test.tsx3

The context-menu literal is now key-for-key identical to basic-context-menu.json's items — checked mechanically, not by eye:

fixture items : ["icon,label","icon,label","icon,label","separator","icon,label"]
test items : ["icon,label","icon,label","icon,label","separator","icon,label"]
IDENTICAL : true

Premise re-derived on today's main

The card measured at 2c3cd1b. Re-measured on origin/maine8e4c4df5f51fc3a1d5ffc678881d3480f97a70e: the two blocks are still at lines 169-173 and 122-124, unmoved.

#7072 has landed — closed as completed by PR #7103, merged. Verified against the tree rather than the card: all four overlay-menu fixtures now grep to 0 occurrences of "value", against a live control of 4 for "label" in the same two files. So the card's future tense is now past tense: the comments were already false on arrival, not prospectively. The PR description says so rather than repeating the card's framing.

Nothing was red, and nothing is red now — stated, not assumed

The literals go to a local renderMenu(items: any[]) helper. Neither file imports MenuItem or MenuItemSchema (zero occurrences; control — sibling suites in the same directory do import from @object-ui/types). Both suites pass identically:

before: Test Files 2 passed (2) Tests 15 passed (15)
after: Test Files 2 passed (2) Tests 15 passed (15)

Same passing count, not merely "green" — a suite collapsed to zero tests also reports no failures.

Why a pin was considered and rejected — decided by measurement

The obvious guard is "parse these literals against MenuItemSchema". Measured: that pin could never fail. Both arms are bare, non-strict z.objects, so zod strips the excess key and reports success:

SUBJECT {label,value,icon} success=true parsed={"label":"Copy","icon":"copy"}
CONTROL+ {label,icon} declared success=true parsed={"label":"Copy","icon":"copy"}
CONTROL- {label,type:"separator"} success=false
CONTROL- {label:42} wrong type success=false

The subject and the clean shape are indistinguishable after parse, while the two negative controls prove the instrument can say no — so this is a real reading, not a dead probe. A parse-based pin would be a phantom check: green before the fix, green after, green again if value came back.

The remaining options were a string-absence assertion over the test's own literal (self-referential — its only failure mode is an edit to the very lines it guards) or deriving the literal from the fixture at test time. The latter is the genuinely load-bearing guard, but it is a different shape in a different package: the repo's own precedent, packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts, reads a catalog fixture from packages/types precisely because "this package's test tsconfig carries no node types". That is its own card, not this one.

So the guard here is a comment, which is the honest instrument for a defect nothing mechanical can catch: the deletion alone is invisible, and a note naming the refusal is what stops the next author (or model) from helpfully adding the key back.

The census the card asked for

The card flagged its own count as "bounded by that query, not a census." Widened, with live controls throughout. Across packages/, exactly two test files transcribe a catalog fixture inline while carrying a key undeclared for that node type — the two in this PR.

Two independent instruments, both non-zero:

The discriminating controls are what make the count trustworthy:

filetranscribes a fixtureverdict
components/command-item-icon.test.tsxyes, 2 fixtures, carrying valuecleanCommandItemdeclaresvalue (types/src/form.ts:1409, zod/form.zod.ts:84)
components/breadcrumb-item-icon.test.tsxyes, exactlyclean — every key declared
cli/check-validity-recogniser.test.tsyes, 2 fixturesclean — the invalid variant: 'small' is the subject under test, and says so
plugin-timeline/timeline-object-bound-gantt-refusal.test.tsxyes, honestly labelled "Trimmed"clean — variant is documented on gantt rows
types/timeline-catalog-fixture-migrated.test.tsreads the real fileclean — the pattern that has no drift class

The command row is the control that matters: the same key spelling, opposite verdict. The instrument distinguishes a phantom key from a declared one rather than pattern-matching on the string.

Surfaced, and deliberately left alone

Verification

Union re-run after the final commit, at 0883c0fc3:

  • pnpm exec vitest run packages/components/221 files, 2027 tests, all passing (vitest project dom — these are .tsx under packages/** and not in heavyDomTests). Run from the repo root with root-relative paths; a package-directory invocation is refused outright by this repo's own guard for pnpm --filter @object-ui/app-shell test 跑的是 @object-ui/console 的 22 个文件,app-shell 自己的 276 个一个没跑,却报绿 #3378, which caught one attempt here — recorded as not measured, not as a pass.
  • type-check (tsc --noEmit && tsc -p tsconfig.test.json) and lint (plain per-package eslint .) → exit 0; 0 errors, 934 warnings, all pre-existing. Coverage confirmed rather than assumed: tsc -p tsconfig.test.json --listFiles puts both edited files in a 1894-file program (control: another package's tests, 0).
  • 11 gates green, each quoting its own verdict line: check-changeset-presence · -no-major · -fixed · -overwrite · check-control-bytes · check-lucide-icon-record-names · check-vi-mock-specifiers · check-vi-mock-inherit · check-shell-escape-residue · check-type-check-coverage · check-lint-coverage.

Changeset: the gate was run, not predicted. It refused the change, and its own text names empty frontmatter as "the explicit exemption and a complete answer to this gate" for a release-nothing change. Added on that basis, and re-run:

✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).
Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
is the explicit exemption and a complete answer to this gate.

Generated by Claude Code

…con transcriptions
`context-menu-item-icon.test.tsx` and `dropdown-menu-item-icon.test.tsx` each
carry an inline transcription of a catalog overlay-menu fixture. Both copies
still spelled `value`, a key no arm of the `MenuItem` union declares
(objectui#6523 narrowed that union deliberately) and no menu renderer reads.
objectui#7072 removed the key from the four fixtures; these copies were outside
that card's fenced surface, so the phantom spelling survived next to the
renderer as copyable example code.
Deleted the 4 + 3 `value` entries. The context-menu block is now key-for-key
identical to `basic-context-menu.json`'s `items`.
Each block also records why the key is absent, because the deletion alone is
invisible to the next author: a parse-based pin could not guard it.
`MenuItemSchema`'s arms are non-strict `z.object`s, so
`{ label, value, icon }` parses with `success=true` and `value` silently
stripped -- indistinguishable from the clean shape. Measured, with live
negative controls: `type: 'separator'` and a wrongly typed `label` are both
refused by the same parse, so the instrument can say no.
Both suites pass identically before and after: 2 files, 15 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3152.5 KB3191.4 KB
Main entry chunk (gzip)142.5 KB350 KB
Entry fileindex-Stz4JheJ.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)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
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)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)244.25KB61.73KB
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.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)65.67KB18.57KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.61KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.93KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.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)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
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)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 06:50
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ Reviewed and armed

All 30 check runs in one call at perPage=100: 27 success, 3 skipped, 0 failed, 0 in_progress. Undrafted, auto-merge SQUASH enabled at 0883c0fc3.

The best result on this card was a decision NOT to write a test

I told you a pin must be able to fail, and to ablate it if you added one. You went further and asked whether one could fail. Probing MenuItemSchema against built dist:

  • SUBJECT {label, value, icon}success: true, with valuesilently stripped, parsing identical to {label, icon}
  • NEGATIVE CONTROL {label, type: 'separator'}success: false
  • NEGATIVE CONTROL {label: 42}success: false

Both union arms are bare non-strict z.object. So a parse pin would be green before the fix, green after it, and green again if the key came back — provably incapable of failing.

Two live negative controls are what make that finding stand up: without them, "the schema accepts it" is indistinguishable from "my probe never ran." Shipping a comment naming the refusal, instead of a green-always test, is the stronger result, and it is only reachable by measuring the instrument rather than trusting it. A test that cannot fail is worse than no test, because it reads as coverage.

A2.2 falsified in the useful direction

objectui#7072 has landed (PR objectui#7103) — verified against the tree, not the card: all four overlay-menu fixtures grep to 0 for value against a live control of 4 for label. So these comments were already false on arrival, not prospectively false. The PR says that rather than repeating the card's future tense, which matters: a PR describing a defect that has not happened yet is a PR nobody can check.

Two NOT MEASURED, named rather than counted

Neither was rounded to red or green.

⚖️ Ruling on the class judgement you put to me: do NOT file a second class card

Your census found five more test files under packages/ transcribing catalog fixtures inline rather than deriving them — command ×2, breadcrumb, cli ×2, plugin-timelineall clean today.

Agreed: no second class card. objectui#6810 already holds the class decision (逐例修 + 补钉), and a duplicate would be noise in a queue where 11 of ~20 candidates measured this session were already not dispatchable.

But the census is real work that would otherwise be lost in a transcript, so it is stated here explicitly and is available to whoever next picks up objectui#6810. The class fix you identify — read the fixture at test time, as packages/types/src/__tests__/timeline-catalog-fixture-migrated.test.ts already does — is the right shape, and objectui#7151 is now testing whether it transplants (that file lives in packages/types specifically because "this package's test tsconfig carries no node types", so it may not).

Filed from the same census

objectui#7151DashboardGridLayout.legacyRetired.test.tsx claims a row is "Byte-for-byte filtered-dashboard.jsonwidgets[0]"; measured, that fixture has never carried that shape, and git log shows fixture and test were added in one commit, so it was never drift — the claim was wrong when written. Same class as objectui#7127. Dispatched, with your shallow-clone bound preserved.

⛔ objectui#7101 correctly left untouched. Your observation for its card — the dropdown array does not transcribe variant at all, so it is a subset of its fixture rather than a verbatim copy — is recorded here rather than added to its surface.


Generated by Claude Code

Merged via the queue into main with commit 85b5077Sep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7102-menu-test-phantom-value branch September 1, 2026 07:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-warren@claude