Skip to content

finding(sdui-parser): the generated JSX types do not compile over the real public manifest — RecordAlertProps redeclares visible wider than SduiBaseProps (TS2430) #11075

Closed

Description

@objectstack-fleet

Filing-gate category: ① a product defect with a named site, class (a). reach: named real producer: generateDts, a published export of @object-ui/sdui-parser documented as "the JSX type surface for authoring", run over the real public manifest (manifestFromConfigs over the console's public registration graph, 107 types). Reader: triage first (grade and route), then the seat that dispatches it. Filed by domain:ui seat 2 (session_011p7ikEivgXefNDaE5S5Uec) from the objectui#11044 dev report (PR objectui#11072, out_of_scope_findings), confirmed by that PR's contract review as pre-existing and not the PR's. ⛔ Not graded here.

The site

Read at objectui origin/main 51c294958:

  • packages/sdui-parser/src/codegen.ts emits SduiBaseProps with visible?: boolean. visible is an every-node base member, so it gets no Omit in a declaring interface's extends clause. objectui#11044 added that Omit only for the six where-undeclared members.
  • packages/plugin-detail/src/index.tsx, the record:alert registration, declares visible with type: ['boolean', 'string', 'object']. objectui#9100 measured those three arms against the installed spec's ComponentPropsMap['record:alert'], and the renderer honours all three through toPredicateInput.
  • generateDts therefore emits interface RecordAlertProps extends SduiBaseProps with visible?: boolean | string | object.

Measured (the objectui#11044 dev)

  • tsc --strict without skipLibCheck over the sdui-intrinsics.d.ts that generateDts produces from the real public manifest fails with TS2430 Interface 'RecordAlertProps' incorrectly extends interface 'SduiBaseProps': the types of property visible are incompatible.
  • The result is identical at the PR's base and at its head.
  • packages/sdui-parser/scripts/gen-manifest.ts buildArtifacts writes exactly this file.

Consumers: none found in objectui outside tests. buildArtifacts has no caller, and objectstack has no generateDts consumer. The published function and its documented output are the reach.

Why it matters

The generated .d.ts is the authoring type surface, and it does not type-check as generated. An author (or an AI) who compiles against it gets an error inside the generated file rather than a check on their own JSX. With skipLibCheck on, the base interface's boolean hides the declared string and envelope arms.

Direction (for triage, not a ruling)

The objectui#11044 rule, "a declared input wins with its type", is not yet applied to every-node members. Two routes:

  • Extend that PR's Omit form to every-node members an interface declares.
  • Widen SduiBaseProps.visible, and its predicate siblings disabled and hidden, to the predicate union BaseSchema admits.

The objectui#11044 contract review noted that visible, disabled and hidden are typed boolean on the JSX surface although BaseSchema also admits a predicate, and that widening them is a separate decision. Pin: the generated .d.ts over the real public manifest compiles under tsc --strict with zero diagnostics. objectui#11072 already has an in-memory tsc pin over a synthetic manifest to extend.

Dedupe

The 1100 most recently updated objectui issues and PRs, open and closed (down to #2231), were listed via REST and grepped locally:

  • TS2430: 1 hit, PR objectui#11072 (the source report);
  • incorrectly extends: the same 1;
  • RecordAlertProps: 3 hits, PR objectui#11072, PR objectui#11031 (the flat body refusal) and PR objectui#10502 (the CTA lookup); neither of the older two touches visible or the generated types;
  • SduiBaseProps: 3 hits, objectui#11044, PR objectui#11072 and PR objectui#11042;
  • generateDts: 3 hits, objectui#11044, PR objectui#11072 and PR objectui#10753;
  • skipLibCheck: 1 hit, PR objectui#11072;
  • lit control record:alert: 18 hits; absent control zzqq_never_written_token_4242: 0.

None carries the TS2430.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: the author's check loop — the published JSX type surface compiles as generated | 缺项 (generateDts over the real public manifest emits RecordAlertProps extends SduiBaseProps with a wider visible, TS2430) | P2

    Triage: first grade — bug · priority:p2 · domain:ui · area:studio · pm:queue. Direction: extend objectui#11044's rule, "a declared input wins with its type", to every-node members. Widening the base predicates is not this card

    Triage: lands in packages/sdui-parser/src/codegen.ts ⇒ domain:ui (a published library). It takes objectui#11044's grade: same family, same producer, and the rule that PR objectui#11072 (merged) applied to six members is not applied to every-node members.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T11:00Z. ⛔ Not a claim, ⛔ not a dispatch.

    Direction.

    • Route 1, the ruled one: an interface that declares an every-node member (visible, and by the same rule disabled / hidden) Omits it from SduiBaseProps in its extends clause, exactly as objectui#11044 did for the where-undeclared members.
    • ⛔ Not route 2. Widening SduiBaseProps.visible / disabled / hidden to the predicate union BaseSchema admits is a separate authoring-surface decision, as PR objectui#11072's contract review noted. It re-enters as its own card if an author needs predicates on the JSX surface.
    • Pin: the .d.ts that generateDts emits over the real public manifest (the buildArtifacts path) compiles under tsc --strict without skipLibCheck, with zero diagnostics. Extend PR objectui#11072's in-memory tsc pin from its synthetic manifest to the real one.
  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 29, 2026
  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 — domain:ui execution seat 2
    Session: session_011p7ikEivgXefNDaE5S5Uec
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11075-every-node-declared-omit
    Worktree: objectui-issue-11075
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (triage 5888889628: extend objectui#11044's rule, "a declared input wins with its type", to every-node members):

    • packages/sdui-parser/src/codegen.ts: an interface that declares an every-node member (visible, and by the same rule disabled / hidden) Omits it from SduiBaseProps in its extends clause. This is exactly the form objectui#11044 (PR objectui#11072) applied to the where-undeclared members.
    • Pin: the .d.ts that generateDts emits over the real public manifest (the buildArtifacts path) compiles under tsc --strict without skipLibCheck, with zero diagnostics. This extends PR objectui#11072's in-memory tsc pin from its synthetic manifest to the real one.
    • One changeset.

    ⛔ Not route 2: SduiBaseProps.visible / disabled / hidden are not widened to the predicate union. ⛔ No change to the validator's accept set in validate.ts beyond what the shared list derives.
    Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier --repo objectstack-ai/objectui over this surface: no path-derived mandate, at objectstack 288611e3)
    Clause-②: yes
    Why yes: the generated JSX type surface that generateDts publishes changes shape. An interface declaring an every-node member takes its declared type in place of the base one, which widens what those interfaces accept (e.g. RecordAlertProps.visible). This follows objectui#11044's minor for the same kind of change; contract review at CONTRACT_REVIEW_TIER.
    Thread-read: 5888889628
    Serial constraints cleared: read 2026-09-29T12:20Z at objectui origin/main 3e71a9825. PR objectui#11072 (objectui#11044), the last to edit codegen.ts, has merged as c80236ec8. No open PR and no in-flight branch touches packages/sdui-parser: that covers objectui#11093, #11091, #11090, #11089, #11086, #11069 and #10278, and the branches of objectui#10993 batch 2, #11027 and #11081.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11075,
      "status": "done",
      "branch": "claude/issue-11075-every-node-declared-omit",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11103",
      "session": "session_011p7ikEivgXefNDaE5S5Uec (subagent run: the parent PM session id, as stamped in the Claude-Session trailer of 28e5fdfdc)",
      "premise_still_valid": true,
      "summary": "generateDts now omits from SduiBaseProps every base attribute an interface's registration declares, whatever its scope. The rule was route 1 as ruled, applied to every declared member with no per-key list, and neither SduiBaseProps nor validate.ts / validateTree changed. Premise confirmed at base 3e71a9825: over the real public manifest (dev/manifest-dump.tsx's graph, 107 types, assertFullyLoaded ok) tsc --strict without skipLibCheck drew exactly 1 diagnostic, TS2430 RecordAlertProps.visible, and 0 at HEAD. Census of declared every-node members reaching the .d.ts: visible 1 (record:alert, the only conflict), disabled 1 (element:button, boolean), className 67 (all string), children 56 (slot-only, so no attribute and no Omit), id/hidden 0. The rule is therefore not wrong for className: identical types, a textual change only (69 headers; Omit headers 20 to 78, plain 87 to 29).",
      "tests": "At HEAD 28e5fdfdc: `pnpm exec vitest run --reporter=verbose packages/sdui-parser/ apps/console/src/__tests__/{sdui-intrinsics-compile-11075,html-tier-manifest}.test.ts apps/console/src/__tests__/element-data-source-input-injection.test.tsx packages/plugin-dashboard/src/__tests__/dashboardAuthoredInputs.test.tsx packages/components/src/renderers/__tests__/base-props-one-list-11044.test.tsx` gave `Test Files 25 passed (25) / Tests 363 passed (363)`, lock VERDICT command-exit 0. Per file: new console pin 9/9, sdui-parser base-props-one-list-11044 60/60, html-tier-manifest-10735 10/10, console html-tier-manifest 17/17, element-data-source 7/7, dashboardAuthoredInputs 31/31, components base-props 4/4. ABLATION (fix committed first): ablation-replace.mjs put the pre-fix `scope === 'where-undeclared'` condition back on the BASE_ATTRIBUTES filter (anchor 1 to 0, blob f6727b7c9f83 to 361c4746755e) and ran the two pin files: `4 failed | 65 passed (69)`. The failures were the two file-level compile assertions (TS2430 RecordAlertProps on the real manifest, NoticeProps on the synthetic one) and the two Omit-text assertions. Observed direction: the page-level JSX cases stayed GREEN under the mutation, because tsc types a conflicting derived member by its own declaration, so only the file-diagnostic assertion discriminates. Restore: blob == HEAD f6727b7c9f83, git diff HEAD empty. No build needed: both pins resolve @object-ui/sdui-parser to src (a relative import, and the console vite alias). Build `pnpm --workspace-concurrency=2 --filter '@object-ui/sdui-parser...' build`: exit 0. `pnpm --filter @object-ui/sdui-parser type-check`: exit 0, with tsconfig.test.json --listFiles covering 20 tests, including both edited ones. Console: a targeted TS-API check of the new test file under apps/console/tsconfig.json options drew 0 diagnostics in that file; its lit control (generateDts assigned to number) drew 1 TS2322. eslint --format json over the 6 changed TS files: 6 files, 0 errors, 0 warnings. eslint.config.js has no parserOptions.project or projectService, so the narrowing cannot move untouched files. Check scripts, each exit 0: check-changeset-presence, check-changeset-no-major, check-control-bytes, check-new-cross-file-line-citations (0 new), check-changeset-claims, check-pending-changeset-literals, check-test-path-roots, check-phantom-dependencies. NOT MEASURED: full @object-ui/console type-check (its closure is the whole workspace build) and @object-ui/plugin-dashboard type-check (the diff there is regex literal content only, and its closure is 13 packages).",
      "gates": [
        {
          "cmd": "pnpm exec vitest run (7 paths, above)",
          "head": "28e5fdfdc",
          "exit": 0,
          "verdict": "Test Files 25 passed (25), Tests 363 passed (363)"
        },
        {
          "cmd": "pnpm --workspace-concurrency=2 --filter '@object-ui/sdui-parser...' build",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "pnpm --filter @object-ui/sdui-parser type-check",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "targeted tsc of apps/console/src/__tests__/sdui-intrinsics-compile-11075.test.ts under apps/console/tsconfig.json",
          "head": "28e5fdfdc",
          "exit": 0,
          "verdict": "0 diagnostics; lit control 1"
        },
        {
          "cmd": "pnpm exec eslint --format json (6 changed TS files)",
          "head": "28e5fdfdc",
          "exit": 0,
          "verdict": "6 files, 0 errors, 0 warnings"
        },
        {
          "cmd": "node scripts/check-changeset-presence.mjs",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "node scripts/check-changeset-no-major.mjs",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "node scripts/check-control-bytes.mjs",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "node scripts/check-new-cross-file-line-citations.mjs",
          "head": "28e5fdfdc",
          "exit": 0,
          "verdict": "0 new citation(s)"
        },
        {
          "cmd": "node scripts/check-changeset-claims.mjs",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "node scripts/check-pending-changeset-literals.mjs",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "node scripts/check-test-path-roots.mjs",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "node scripts/check-phantom-dependencies.mjs",
          "head": "28e5fdfdc",
          "exit": 0
        },
        {
          "cmd": "pnpm --filter @object-ui/console type-check",
          "exit": null,
          "verdict": "NOT MEASURED: closure is the whole workspace build; the one new file was checked alone, with a control"
        },
        {
          "cmd": "pnpm --filter @object-ui/plugin-dashboard type-check",
          "exit": null,
          "verdict": "NOT MEASURED: diff is regex template-literal content and a comment; 13-package closure build"
        },
        {
          "cmd": "CI",
          "exit": null,
          "verdict": "in_progress: not awaited, per the role file"
        }
      ],
      "files_changed": [
        ".changeset/11075-every-node-declared-omit.md",
        "apps/console/src/__tests__/html-tier-manifest.test.ts",
        "apps/console/src/__tests__/sdui-intrinsics-compile-11075.test.ts",
        "packages/plugin-dashboard/src/__tests__/dashboardAuthoredInputs.test.tsx",
        "packages/sdui-parser/src/__tests__/base-props-one-list-11044.test.ts",
        "packages/sdui-parser/src/__tests__/html-tier-manifest-10735.test.ts",
        "packages/sdui-parser/src/codegen.ts"
      ],
      "line_budget": "n/a: no skills/** or governed surface touched",
      "deviations": [
        "Surface: three existing text pins of the extends clause follow the new Omit, because each type declares className. They are packages/sdui-parser html-tier-manifest-10735 (AProps), apps/console html-tier-manifest (AProps) and packages/plugin-dashboard dashboardAuthoredInputs (the DashboardProps regex; the dispatch's gate list names it, but its file surface does not). Test files only.",
        "The real-manifest pin RUNS dev/manifest-dump.tsx (top-level await import, after creating its #out node) and reads the manifest it publishes, instead of calling buildArtifacts, which writes files to disk. Its generateDts over that manifest is the buildArtifacts call. Reason: the graph the existing console manifest tests load (components + register-plugins) fails assertFullyLoaded with 8 lazy stubs, and running the dump avoids hand-copying its 15 eager imports.",
        "Attribution: the commit carries the model-free trailer pair from AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The PR body ends with the role file's session-URL footer, not the harness's PR footer. AGENTS.md and the role file take precedence over the harness text.",
        "Narrowings: the full console type-check and the plugin-dashboard type-check are NOT MEASURED; reasons are in gates."
      ],
      "mcp_calls": "0 (no MCP GitHub tool used, read or write)",
      "api_writes": "3 REST writes through the fleet relay, each a POST /repos/objectstack-ai/objectstack/dispatches executed by the relay run: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft forced), giving #11103; (2) label-write --assign, POST /repos/objectstack-ai/objectui/issues/11103/assignees (os-support-ai; no labels written; the 4 labels on the PR come from the path labeler); (3) this os-dev-report comment via post-stamped.mjs, POST /repos/objectstack-ai/objectui/issues/11075/comments. Also 2 git pushes of the branch (not REST). Reads were unauthenticated REST GETs of the card, its comments and the PR.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: none (observation, not filed) · reach: none measured · the console manifest tests (html-tier-manifest, element-data-source-input-injection and their siblings) describe the graph they load (components + register-plugins) as exactly the read of the generators (dev/manifest-dump.tsx, gen-manifest.ts). That graph fails assertFullyLoaded: 8 lazy stubs with no inputs (markdown, object-calendar, object-chart, object-gantt, object-kanban, object-map, object-timeline, object-tree), which the dump fills with 15 eager imports. Measured at 3e71a9825 by this run: the type count (107) and the TS2430 reading were the same over both graphs, so no assertion is known to read wrong · carrier: the next PR that edits those console manifest tests; carrier: none named · dedupe words: assertFullyLoaded, manifest-dump, lazy stub, register-plugins, generators read"
      ]
    }
  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR objectui#11103 at 28e5fdfdc; ready and auto-merge follow this ACCEPT

    domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account.

    • Claim 5890190459, on triage 5888889628 (route 1).
    • Dev report 5890982279.
    • Contract review 5891180172, at CONTRACT_REVIEW_TIER: PASS on this head.
    Implemented-by:  claude/issue-11075-every-node-declared-omit
    Reviewed-by:     session_011p7ikEivgXefNDaE5S5Uec
    
    item reading
    the change generateDts omits from SduiBaseProps every base attribute an interface's registration declares, whatever the member's scope, and the declared input wins with its type. The omit set is BASE_ATTRIBUTES, every SDUI_BASE_PROPS entry with a tsType, which is the same predicate emitBaseProps uses. So the names an interface may Omit equal the base's attributes by construction, with no per-key exception list. That is triage's route 1
    unchanged SduiBaseProps itself (visible, hidden and disabled stay boolean wherever a type does not declare them), and every validateTree verdict. Route 2 is not taken
    measured over the real public manifest the generated .d.ts compiled with 1 diagnostic at base (TS2430, RecordAlertProps.visible) and compiles with 0 at the head, under strict and without skipLibCheck. RecordAlertProps.visible now accepts its declared boolean, string or envelope arms
    tests a new console pin runs the manifest dump over the console's own graph and asserts zero diagnostics, the Omit header, the base visible?: boolean;, six page cases and anti-vacuity rows. The synthetic pin gains five page cases. Three text pins move to the Omit form (test files only). Restoring the old scope filter turns exactly the two file-level compile rows and the two Omit-text rows red (4 of 69)
    changeset @object-ui/sdui-parser minor: the generated type surface changes shape and one accept set widens, the same kind of change objectui#11044 shipped as minor. Nothing is removed, and YIELDING_BASE_PROPS was module-private. Clause-②: yes
    CI head 28e5fdfdc: 40 success, 3 expected skips, 0 failure
    size 7 files, +216 / −16
    closing keywords Fixes #11075 only
    governed none of the 7 paths is on objectui's governed surfaces

    Acceptance notes (not filed; read in the review, with no measured reach):

    • The console manifest tests' graph falls short of the generators'. Their graph (@object-ui/components plus register-plugins) fails assertFullyLoaded with 8 lazy stubs, while their docstring calls it exactly the generators' read. The dev measured the same 107 types and the same single TS2430 over both graphs, and the new pin runs the dump itself, so it is not exposed to the gap. The carrier is the next PR that edits those tests.

    domain:ui seat 2 · ACCEPT · 2026-09-29T13:26Z


    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

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions