Skip to content

refactor(components): the action keys publish UIActionSchema, and every forwardRef renderer annotates its props (#4418, #4422) - #4438

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-4418-4422-action-typing-integrity
Aug 12, 2026
Merged

refactor(components): the action keys publish UIActionSchema, and every forwardRef renderer annotates its props (#4418, #4422)#4438
yinlianghui merged 2 commits into
mainfrom
claude/issue-4418-4422-action-typing-integrity

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4418
Fixes #4422

Two halves of one defect, on one branch because they share the same files and the same mechanism: the forwardRef props collapse (#4422) is why the action renderers could declare the deprecated ActionSchema while being written against UIActionSchema (#4418) for as long as they did. Fixing the collapse turns that drift into a hard compiler error, so neither half lands alone.

Red-first: the probe, both directions

The in-situ test #4422 specified, const probe: null = schema; in action-bar.tsx. Under strictNullChecks only any accepts it.

state result
before any annotation tsc --noEmit exit 0, zero diagnostics — the hole
after the annotation error TS2322: Type 'ActionBarSchema' is not assignable to type 'null'. exit 2

Note the polarity: the healthy state is the one that raises an error. A green probe is the defect.

Swept mechanically across all 15 sites (probe inserted, tsc run, file restored, one at a time):

action-bar.tsx        PROBE REJECTED -> TS2322: Type 'ActionBarSchema' is not assignable to type 'null'
action-group.tsx      PROBE REJECTED -> TS2322: Type 'ActionGroupSchema' ...
action-menu.tsx       PROBE REJECTED -> TS2322: Type 'ActionMenuSchema' ...
action-button.tsx     PROBE REJECTED -> TS2322: Type 'ActionSchema & { type: string; className?: str...
action-icon.tsx       PROBE REJECTED -> TS2322: Type 'ActionSchema & { type: string; className?: str...
basic/div.tsx         PROBE REJECTED -> TS2322: Type 'DivSchema' ...
basic/icon.tsx        PROBE REJECTED -> TS2322: Type 'IconSchema' ...
basic/separator.tsx   PROBE REJECTED -> TS2322: Type 'SeparatorSchema' ...
basic/span.tsx        PROBE REJECTED -> TS2322: Type 'TextSpanSchema' ...
form/button.tsx       PROBE REJECTED -> TS2322: Type 'ButtonSchema' ...
layout/card.tsx       PROBE REJECTED -> TS2322: Type 'CardSchema' ...
layout/container.tsx  PROBE REJECTED -> TS2322: Type 'ContainerSchema' ...
layout/stack.tsx      PROBE REJECTED -> TS2322: Type 'StackSchema' ...
basic/html-elements.tsx  probe accepted (schema is any)
layout/semantic.tsx      probe accepted (schema is any)

13 renderers recover a real declared type. The last two accept the probe honestly — their props type declares schema: any (one factory over every raw HTML tag / seven semantic tags, none of which has a schema type). Nothing was erased there, so nothing is recovered; they are annotated anyway so the guard needs no per-file carve-out.

#4422 — the shape, and why direction 1 needed one more move than the card said

The card's direction 1 is "annotate the render function's parameter directly in addition to the type argument". Measured: that does not compile.

error TS2345: Argument of type '({ schema, ... }: ActionBarRendererProps, ref) => Element | null'
  is not assignable to parameter of type 'ForwardRefRenderFunction< HTMLDivElement, Omit< ActionBarRendererProps, "ref" > >'.
    Property 'schema' is missing in type 'Omit< ActionBarRendererProps, "ref" >'
      but required in type 'ActionBarRendererProps'.

The compiler states the mechanism itself: Omit has already erased schema, so an annotation that requires schema is contravariantly incompatible with the collapsed parameter. The two halves have to move together. The shape that works, and that this PR applies uniformly:

forwardRef< El, { schema: XSchema; className?: string } >(
  ({ schema, className, ...props }: { schema: XSchema; className?: string; [key: string]: any }, ref) => 
)

The index signature moves off the type argument and onto the parameter annotation. Both halves load-bearing:

  • off the type argument, PropsWithoutRef has nothing to collapse and the declared props survive;
  • on the parameter, ...props still collects arbitrary keys for the DOM / Shadcn hand-off — unchanged spread, unchanged runtime.

This is not the deferred direction 2. No component's real prop surface is enumerated and no pass-through is removed; the signature is relocated, not deleted. It is consumer-neutral, measured rather than assumed: none of the 15 renderer consts is exported, all 15 have 0 JSX call sites repo-wide, and Registry.register takes ComponentRenderer< T = any > = T. The type argument's index signature had exactly one observable effect — the collapse.

#4418 — the ref inventory, measured at the branch point

Re-measured on origin/main @ 306c10136 rather than taken from the card. The card's "~46 refs across 12 files" is the loose substring count (it also matches UIActionSchema, which #4417 had just introduced); at my branch point that reads 54 occurrences / 12 files. The migration surface is the word-boundary count: 25 bare ActionSchema references across 10 files, of which 11 are type positions and 14 are prose.

file type positions migrated prose refs updated
action/action-bar.tsx import, actions, systemActions 3 4
action/action-menu.tsx import, actions 2 2
action/action-group.tsx import, actions 2 1
action/action-button.tsx import, ActionButtonProps.schema 2 1
action/action-icon.tsx import, ActionIconProps.schema 2 0
action/index.ts 0 1 (left: describes the family)
layout/containers.tsx 0 0 (left: 2 refs, both about @objectstack/spec's ActionSchema.visible and a variant note — a different subject)
3 test files 0 0 (left: same, prose about the spec type)
total 11 8

action-button.tsx and action-icon.tsx are the siblings the sweep found: the card's scope list missed them because their index signature and their ActionSchema both hide behind a named interface (ActionButtonProps / ActionIconProps) rather than an inline type. Same defect, same mechanism, same package, and they hold 4 of the 11 type positions — so the guard's matcher pins that spelling explicitly (test case 2).

Per-ref runtime-acceptance check

The ruling's stop condition: if migration would change runtime acceptance of working metadata, stop that ref and report. Measured per ref rather than assumed.

The question reduces to a measurement, because the six migrated declarations have no type-checked consumer outside their own file. A repo-wide sweep for ActionBarSchema|ActionMenuSchema|ActionGroupSchema|ActionButtonProps|ActionIconProps returns 12 hits, every one inside the declaring file (its own interface, its own forwardRef, and comments). None is re-exported from packages/components/src/index.ts. Every production construction site — app-shell's RecordDetailView / EnvironmentListToolbar / ObjectView, plugin-detail's DetailView, core's public-blocks — builds a plain object and hands it to SchemaRenderer, whose schema prop is any, so no authoring site is judged by these types in either direction.

migrated ref legacy-only values passed by any consumer? verdict
ActionBarSchema.actions none — 0 external type-checked consumers migrate
ActionBarSchema.systemActions none migrate
ActionMenuSchema.actions none migrate
ActionGroupSchema.actions none migrate
ActionButtonProps.schema none; actionType (the one legacy-shaped key the renderer reads) is kept on the intersection migrate
ActionIconProps.schema none migrate

Refs stopped: none. A repo-wide sweep for the legacy literal type: 'action' finds 28 sites and not one of them feeds these keys — they are @object-ui/types' own crud.ts tests, and NavigationItem-shaped nav entries in layout / app-shell (a different schema that happens to share the string). Examples and apps fixtures were included in the sweep.

The direction of the incompatibility is worth recording, because it is not one of #4417's four proofs. Those were modern-value-into-legacy-annotation. This one is the reverse — legacy-declaration-into-modern-annotation — and it lands on name:

error TS2322: Type 'crud.ActionSchema[]' is not assignable to type 'ui-action.ActionSchema[]'.
    Types of property 'name' are incompatible.
      Type 'string | undefined' is not assignable to type 'string'.

UIActionSchema requires name; legacy inherits it as optional from BaseSchema. That is the tightening, and it reaches no authoring site for the reason measured above.

The guard

packages/components/src/__tests__/forwardref-props-annotation.guard.test.ts — ratchet style, modelled on app-shell/src/no-component-any-cast.ratchet.test.ts (the repo's structural-pin convention). It walks packages/components/src production sources with the real TypeScript AST rather than a regex (typescript is already a declared devDependency of the package, so check:phantom-deps stays green), and judges every forwardRef whose render function reads a schema prop on two independent clauses:

  1. the render function's first parameter carries a direct type annotation;
  2. the props type argument carries no string index signature.

Plus two anti-vacuity tests: the scan must find the population (floor 12; 15 today), and the matcher must detect the shapes it bans — compiled in memory, no fixture files — pinning the exact pre-fix inline shape, the named-interface spelling that made the card undercount by two, an alias-plus-intersection spelling, the compliant shape reading as compliant, and a number index signature correctly not firing (it does not put string into keyof, so it does not trigger the collapse).

Scope limits are stated in the file rather than implied: production sources only, schema-reading forwardRefs only, and index signatures detected syntactically (inline type, or a type/interface declared in the same file) — a props type imported from another module is out of a source scan's reach and is not claimed to be covered.

Reverse verification — direction predicted before each run

Method: git checkout / scratch edit, never git stash.

A. Revert one file's annotation. Predicted: basic/div.tsx goes red on both clauses, no other file moves, and the probe there starts passing again. Measured exactly that — 2 failed | 2 passed, both failures naming only renderers/basic/div.tsx:41:

AssertionError: expected [ 'renderers/basic/div.tsx:41' ] to deeply equal []

and with the probe re-inserted into the reverted file, tsc --noEmit exit 0schema is any again. Restored.

B. Violate clause 2 only. Put [key: string]: any back on action-menu.tsx's type argument while keeping the annotation. Predicted: clause 2 red naming that file alone, clause 1 stays green, and tsc red with TS2345. Measured exactly that — 1 failed | 3 passed, + "renderers/action/action-menu.tsx:178", and:

src/renderers/action/action-menu.tsx(179,3): error TS2345: ...
    Property 'schema' is missing in type 'Omit<{ [key: string]: any; schema: ActionMenuSchema; ... }, "ref">'
      but required in type '{ [key: string]: any; schema: ActionMenuSchema; ... }'.

Restored; tree clean, tsc exit 0, guard 4/4 green.

Together these cover both halves of the trap: A is the compiler-invisible case (guard red, tsc green — exactly the state this package shipped in), B is the compiler-visible one. A guard that only caught B would not have caught the bug that was actually there.

Verification

  • pnpm exec vitest run packages/components/ --maxWorkers=2 (repo root) — 122 files / 1082 tests passed, exit 0. Baseline was 121 / 1078; this adds one file with four tests.
  • tsc --noEmit and tsc -p tsconfig.test.json for @object-ui/components — both exit 0, before and after the origin/main merge. Dependency closure built first (pnpm --workspace-concurrency=2 --filter '@object-ui/components^...' build) — the suffix ^... form, i.e. the packages components depends on, since a stale dist/*.d.ts lies in both directions.
  • Repo-wide turbo run type-check --concurrency=2 — 78 successful, 78 total. This is the load-bearing no-downstream-red proof, and it is the downstream direction by construction: turbo runs every package, so it subsumes --filter '...@object-ui/components' (the prefix/consumers form) rather than the suffix/dependencies one.
  • eslint on the 16 changed files — 0 errors. Warnings 81 → 83 (+2), measured against the same files at the branch point in a scratch worktree.
  • check-control-bytes (4144 files) / check-phantom-dependencies (40 packages, 12759 specifiers) / check-action-forward-parity / check-changeset-presence / check-changeset-no-major — all green. Plus a direct control-byte scan of the changed files: clean.

Lint delta, honestly

+2, and all of it is layout/semantic.tsx (2 → 4). Every other one of the 15 files is net zero — the index signature moved rather than multiplied. semantic.tsx was forwardRef< HTMLElement, any >: a single bare any covering everything. Writing the shape out as { schema: any; className?: string } plus the pass-through annotation spells that same any three times where the blanket spelled it once. The anys are not new — the honesty is. Reporting it rather than suppressing it, per #4417's precedent.

Changeset

minor, per the ruling and the #4403 precedent, with the breaking semantics written out in the body — six exported declarations change the action type they name, and the two types are not interchangeable in either direction. Never major: .changeset/config.json's fixed group means any major would push all 39 packages off @objectstack's major, which check-changeset-no-major.mjs enforces mechanically.

patch would have been wrong here in the way #4417's patch was right: that PR changed no exported declaration's shape, this one changes six.

Scope

packages/components/** plus the changeset, nothing else. packages/plugin-chatbot, packages/plugin-grid, packages/plugin-dashboard and packages/fields untouched; @object-ui/types read-only and untouched — the migration consumes UIActionSchema from it and needed no types-side change. origin/main advanced twice during the work (#4429/#4430, then #4424/#4436); merged in, neither touches components, types, core or react, and the full suite was re-run after the merge.

Deviations from the dispatch, all measured

  1. 13 renderers annotated with a recovered type, not 11 — 15 forwardRefs touched in total. The card's file list was a grep artifact: it matched inline [key: string]: any inside the forwardRef type argument and so missed action-button.tsx and action-icon.tsx, whose identical defect hides behind a named props interface. Both sit inside finding(components): the action renderers declare the deprecated ActionSchema but are written against UIActionSchema #4418's own migration set, so leaving them would have shipped a guard with a known hole on day one. html-elements.tsx and semantic.tsx are annotated too — no type is recovered there (schema is genuinely any), but it keeps the guard carve-out-free.
  2. The index signature moves off the forwardRef type argument. Direction 1 as literally worded does not compile (TS2345, quoted above). This is the minimum additional move that makes it compile, and it is not deferred direction 2 — see the measurement under finding(components): forwardRef + a props index signature silently erases every declared prop type — 11 renderers affected #4422 above.

Out of scope

No new findings to file. The two follow-ups this PR deliberately does not take are already the card's own deferred directions 2 and 3 (dropping index signatures per-component / a shared props helper), which interact with the #4425 whitelist question.


Generated by Claude Code

claude added 2 commits August 12, 2026 06:36
…renderers annotate their props (#4418, #4422)

The three action schema interfaces plus ActionButtonProps/ActionIconProps
migrate their action keys from the @deprecated legacy ActionSchema (crud.ts)
to UIActionSchema (ui-action.ts) — the type the implementations were already
written against.

All fifteen schema-reading forwardRef renderers annotate their render
function's first parameter directly, with the pass-through index signature on
the annotation rather than on the forwardRef type argument, so PropsWithoutRef
no longer collapses the declared props to a bare index signature. A structural
guard pins both halves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 12, 2026 6:59am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-B7R9OEpn.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 489.20KB 108.43KB
core (index.js) 2.99KB 1.14KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 153.42KB 41.19KB
fields (index.js) 228.99KB 56.82KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.33KB 1.20KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.98KB 10.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 62.01KB 17.63KB
plugin-chatbot (index.js) 181.17KB 43.03KB
plugin-dashboard (index.js) 120.75KB 31.38KB
plugin-designer (index.js) 211.16KB 42.76KB
plugin-detail (index.js) 239.03KB 59.77KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.58KB 27.68KB
plugin-gantt (index.js) 164.14KB 39.98KB
plugin-grid (index.js) 187.99KB 49.92KB
plugin-kanban (index.js) 48.60KB 13.41KB
plugin-list (index.js) 110.21KB 26.79KB
plugin-map (index.js) 18.05KB 5.80KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.99KB 10.74KB
plugin-timeline (index.js) 26.21KB 7.52KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.96KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

ACCEPT — PM 复核 (session session_017Qqyix2QcnpUC9XeYVDzx3), closes #4418, #4422.

  • The direction-1 correction is measurement fixing my ruling's mechanics, and it is accepted in full: "annotate in addition to the type argument" is contravariantly impossible once Omit has erased schema, so relocating the index signature to the parameter is the honest minimal fix — and the report's proof that this is NOT deferred direction 2 (nothing enumerated, spread unchanged, the type argument's signature had exactly ONE observable effect, consumer-neutrality measured at zero exports/zero call sites) is what makes the relocation safe rather than smuggled.
  • The guard is the family's best: real-AST clauses covering both halves (A: guard-red/tsc-green — the shipped hole; B: both red), anti-vacuity floor plus in-memory matcher pinning five spellings including the named-interface one the card's grep missed, and its syntactic-reach limit stated in the file rather than implied.
  • Scope+2 accepted (a guard with a known day-one hole is worse than honest scope growth; the two files were finding(components): the action renderers declare the deprecated ActionSchema but are written against UIActionSchema #4418's anyway), the ref-count reconciliation (word-boundary 25 vs substring ~46, prose split with different-subject reasoning) is the reading-governs standard, and the minor grading with the exact contrast to feat(components): compile under noImplicitAny — type the 26 renderer signatures + 2 test sites #4417's patch is correct — six exported declarations move here, zero moved there.
  • The name-tightening risk is stated with its zero-judged-today measurement; the +2 lint delta traced to spelling out semantic.tsx's blanket any. 78/78, CI converged.

Flipping ready + arming auto-merge.


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 12, 2026 07:11
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit dc2aa3e Aug 12, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4418-4422-action-typing-integrity branch August 12, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants