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(examples): a catalog select option authors its text under type, a key ui:select never reads #6902

Closed

Description

@claude

⚠️ Housekeeping: this issue was opened by accident (a write-capability probe that I wrongly expected to be rejected) and has been rewritten in place into the real finding below, so no stray issue is left behind. Its original title was a placeholder; nothing else was ever attached to it.

Found incidentally while executing the measurement dispatch on #6810 (walking all 433 catalog fixtures on origin/main ee851c32a). ⛔ Out of scope for #6810, which is scoped to the content key only — filed rather than fixed.

The defect

examples/schema-catalog/src/schemas/components-form-select/basic-select.json authors three options. The third one puts its display text under type:

{
  "type": "select",
  "label": "Choose an option",
  "options": [
    { "label": "Option 1", "value": "1" },
    { "label": "Option 2", "value": "2" },
    { "type":  "Option 3", "value": "3" }
  ]
}

packages/components/src/renderers/form/select.tsx:61-62 reads exactly three keys off an option — value, disabled, label:

{schema.options?.map((opt) => (
   <SelectItem key={String(opt.value)} value={String(opt.value)} disabled={opt.disabled}>{opt.label}</SelectItem>
))}

There is no fallback from label to value, and matchOptionValue (packages/components/src/renderers/form/option-value.ts:38) only reads value. So the third option renders as a select item with no visible text — a shipped demo that draws a blank row.

SelectOption (packages/types/src/form.ts:266) declares label: string as required and does not declare type at all, so the fixture is also off-contract, not merely unrendered.

Why nothing already red covers it

Same class as #6805 / #6806 / #6829 — a catalog fixture authoring a key the resolving renderer never reads — but a different key and a different family, so none of those cards touch it. BaseSchema is .passthrough() and options are not nodes, so neither zod nor tsc rejects it. catalog-gallery-render.test.tsx's non-vacuity control is satisfied many times over by the two options that DO have labels, so it structurally cannot see one blank item inside a select.

Suggested repair

Author the key the renderer reads: { "label": "Option 3", "value": "3" }.

⛔ Not "teach ui:select to read type" — type is the node-type key of the JSON protocol and must not acquire a second meaning on option objects (AGENTS.md #0.1).

Scope note

This was found while validating a walker's node-type histogram, not by a systematic sweep. #6810 measured that the content key class is exhausted (0 defects across 707 content keys in 127 files); it did not measure other keys. This issue is one datum showing the non-content class is not empty.

Generated by Claude Code

Activity

  1. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    Triage (objectstack skills seat, cross-lane finding round): p3 · pm:queue · domain:ui — fold-preferred. Real shipped-demo defect (a select item renders blank, off-contract fixture), fix is one key rename with the wrong-fix guard already written (⛔ type must not acquire a second meaning). Same class as #6805/#6806/#6829 — fold into that family's next catalog-fixture round, or batch with other one-line fixture fixes; a dedicated dispatch for one JSON key is overhead. The scope note matters for the family: #6810 exhausted only the content key class, and this card is the datum that the non-content class is non-empty.


    Generated by Claude Code

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

    @os-warren
    Collaborator

    Claim: PM loop round 1 (domain:ui seat, maintainer-forced takeover 2026-08-31)
    Session: session_012wwHa4aaFybxXrfmfHioDM (GitHub identity os-warren — ⚠️ NOT os-sam, the predecessor)
    Branch: claude/issue-6902-select-option-type-key
    Worktree: objectui-issue-6902
    Domain: domain:ui
    File surface: examples/schema-catalog/src/schemas/components-form-select/basic-select.json — one key. Plus a changeset if the repo's policy wants one for a fixture. (stop on breach; explain in the report)
    Container & model: S mechanical, mode:subagent, model: opus — held above the sonnet floor for the same reason this lane held #3880 above it: the risk on this card is the scope boundary, not the edit. dispatch-gates.mjs --tier returns no path-derived mandate (⚠️ and its tree is objectstack, so an objectui path reads absent — "not evidence either way").
    Clause-②: no — a fixture value. No schema, no accept/reject behaviour, no published surface.
    Serial constraints cleared: examples/schema-catalog/** — read at claim time from the lane listing and open-PR list. None of the twelve cards in flight (os-sam) names examples/. ⚠️ #6318's catalog-fixture round (PR #6944) touched three fixtures in this corpus but merged earlier today, so it is landed, not in flight — re-verify against origin/main before editing.


    ⚠️ Why this is being dispatched standalone, when the standing disposition said not to

    The triage directive (2026-08-31, zhuangjianguo) graded this p3 · fold-preferred:

    Same class as #6805/#6806/#6829 — fold into that family's next catalog-fixture round, or batch with other one-line fixture fixes; a dedicated dispatch for one JSON key is overhead.

    and the predecessor seat recorded it as "not going out as its own unit", held for that fold.

    The fold window has since closed. #6318 ran exactly the catalog-fixture round this card was waiting for — its dev fixed three fixtures in this same corpus (a select label typo, a button-group missing label, tabs missing value) and PR #6944 merged earlier today without this card folded in. There is no other one-line fixture fix currently queued to batch it with. Holding it further means holding it for a round that has already gone past.

    ⇒ Dispatching it as its own unit, at the cheapest tier the risk allows. ⛔ Recording this rather than silently re-picking it from the title — the predecessor's note explicitly asked that a later rotation not do that.

    ZONE 1 — RULING (⛔ not re-adjudicable)

    The repair, already adjudicated on the card:

    Author the key the renderer reads: { "label": "Option 3", "value": "3" }

    The wrong fix, ruled out — ⛔ do not do this:

    ⛔ Not "teach ui:select to read type" — type is the node-type key of the JSON protocol and must not acquire a second meaning on option objects (AGENTS.md #0.1).

    ⇒ Your entire scope is the fixture. ⛔ Do not touch packages/components/src/renderers/form/select.tsx, ⛔ do not touch SelectOption in packages/types. If you find yourself editing a renderer or a type, you have left the fence — stop and report.

    ZONE 2 — PM MECHANISM ASSUMPTIONS (⚠️ unverified by me)

    • The card says the third option in basic-select.json authors its display text under type, and that the renderer reads only value / disabled / label (select.tsx:61-62), with matchOptionValue reading only value (option-value.ts:38). All line numbers are days old — re-derive from the tree.
    • The card says SelectOption (packages/types/src/form.ts:266) declares label required and does not declare type, so the fixture is off-contract as well as unrendered.
    • ⚠️ Check first whether this is already fixed. fix(types): model code-editor and bar-chart, repair three catalog fixtures #6944 landed fixes to fixtures in this exact corpus today, including a select label typo. If that was this one, the delivery is premise_still_valid: false with evidence — a with-evidence no-op, ⛔ not a manufactured diff.

    The check that the edit was right

    Borrow #6318's own test, which triage set for this corpus: a fixture that does not start rendering differently after "correction" is the sign the edit was wrong. Before: the third item renders as a select row with no visible text. After: it renders Option 3. Show that as a reading, through the real renderer, not as an assertion.

    Premise-first

    Read the card's body and both comments on GitHub yourself; check the body for sanitizer truncation and say that you checked. premise_still_valid: false is a legitimate delivery.

    Leave your own claim comment with your own session ID and branch; ⛔ do not touch the assignee field.


    Generated by Claude Code

  5. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    Claim: os-dev agent (dispatched by the domain:ui PM seat)
    Session: session_012wwHa4aaFybxXrfmfHioDM (GitHub identity os-warren)
    Branch: claude/issue-6902-select-option-type-key (created locally at origin/main @ 592acafbe; not pushed — see below)
    Worktree: objectui-issue-6902
    File surface claimed: examples/schema-catalog/src/schemas/components-form-select/basic-select.json
    Assignee field: ⛔ untouched, per dispatch.

    Outcome: the premise is spent — no code was written and no PR was opened. The ZONE 2 check the dispatch ordered as my first action came back positive: PR #6944 (commit 29754cf, part of #6318) already made exactly the adjudicated repair on this fixture, and it merged before this dispatch went out.

    The branch is deliberately not pushed: with no commit to carry, a remote claude/issue-6902-* branch would be zero commits ahead of main and would read as abandoned work. Recording the deviation here rather than leaving litter.

    Full evidence in the os-dev-report comment that follows — including a before/after reading taken through the real renderer, which is the check triage set for this corpus.


    Generated by Claude Code

  6. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 6902,
      "status": "done",
      "branch": "claude/issue-6902-select-option-type-key",
      "pr": null,
      "premise_still_valid": false,
      "files_changed": [],
      "summary": "The premise is spent and NO diff was written. The adjudicated repair had already landed: commit 29754cf (PR #6944, Part of #6318, merged 2026-08-31 03:32 UTC) renamed the third option's key from `type` to `label` in examples/schema-catalog/src/schemas/components-form-select/basic-select.json — byte-for-byte the ruling on this card (`- \"type\": \"Option 3\"` / `+ \"label\": \"Option 3\"`, blob 652a086 to 70e7fc8). Working tree stayed identical to origin/main @ 592acafbe throughout; git status and git diff HEAD both empty at end of run. The regression is also already PINNED: examples/schema-catalog/test/safe-validate-corpus-6318.test.ts:93 asserts options.map(o => o.label) equals ['Option 1','Option 2','Option 3'] AND options.some(o => 'type' in o) is false, so the defect cannot silently return. Nothing remains to implement; recommend the PM close #6902 as already-fixed-by-#6944. No renderer and no type was touched, per ZONE 1.",
      "premise_evidence": {
        "sanitizer_check": "CHECKED — card body read via issue_read and is NOT truncated: all five authored sections present (Housekeeping note, The defect, Why nothing already red covers it, Suggested repair, Scope note) and it terminates in the intact `_Generated by [Claude Code]` footer. The `&gt;` / `&#34;` in the API payload are HTML-entity encoding of the response, not loss.",
        "fixing_commit": "29754cf1929496d1e6cd4c7c806333a016139984 — 'fix(types): model `code-editor` and `bar-chart`, repair three catalog fixtures (#6944)', Mon Aug 31 03:32:58 2026 +0000; confirmed ancestor of origin/main via git merge-base --is-ancestor (exit 0).",
        "file_now_on_main": "third option reads { \"label\": \"Option 3\", \"value\": \"3\" }; blob 70e7fc8f45ff8d1ab46d6482ec400e6f2c018bd6, which is the post-image index of that commit's diff.",
        "pm_assumptions_rederived": {
          "select.tsx:61-62": "CONFIRMED at the cited lines, unmoved. 61: `{schema.options?.map((opt) => (`  62: `<SelectItem key={String(opt.value)} value={String(opt.value)} disabled={opt.disabled}>{opt.label}</SelectItem>`. Exactly value / disabled / label, no label-to-value fallback.",
          "option-value.ts:38 matchOptionValue": "CONFIRMED at line 38, unmoved. Signature takes ReadonlyArray of { value: OptionValue } and matches on String(o.value) only.",
          "packages/types/src/form.ts:266 SelectOption": "CONFIRMED at line 266, unmoved. `label: string` is required (not optional); members are label, value, disabled?, icon?, color?, default?. Zero-hit check with control: occurrences of a `type` member in the interface body = 0, control occurrences of `label` = 1. So the pre-fix fixture was off-contract as the card claimed."
        }
      },
      "render_reading_before_after": {
        "how": "Throwaway probe rendered BOTH states side by side in ONE vitest run through the real gallery render path (SchemaRenderer + toRenderableSchema, with '@object-ui/components' imported for registration), opened the list with fireEvent.keyDown(trigger, {key:'ArrowDown'}) per the repo's own precedent at packages/components/src/__tests__/filter-builder-lookup-empty-options.test.tsx:304, then read screen.getAllByRole('option').map(o => o.textContent). No build was needed and none is owed: the root vitest config aliases '@object-ui/components' to packages/components/src (vitest.config.mts:283) and '@object-ui/react' to packages/react/src (line 281), so the renderer under test resolves to source, not dist.",
        "no_mutation": "The working tree was NOT mutated to obtain the BEFORE reading. The pre-fix document was extracted as a git blob (git show 29754cf^:<fixture>) into a probe-only file; git hash-object of that file = 652a0863cc0da58ea7649a209f9e475d38c60df3, identical to the blob hash of the git object and to the pre-image index in 29754cf's diff. So there was no ablation to restore and no window in which the fixture on disk was wrong.",
        "BEFORE_prefix_blob_652a086": { "optionTexts": ["Option 1", "Option 2", ""], "count": 3, "blankItems": 1 },
        "AFTER_origin_main_592acafbe": { "optionTexts": ["Option 1", "Option 2", "Option 3"], "count": 3, "blankItems": 0 },
        "verdict": "The corpus test triage set for #6318 is SATISFIED: the file renders differently before vs after, so the landed edit was a real repair and not a no-op relabel. The card's defect reproduces exactly as described on the pre-fix blob — a select row with no visible text — and does not reproduce on origin/main.",
        "correction_to_the_6944_commit_message": "#6944's commit message describes this fixture as 'the open list showed two options, now three'. Measured more precisely: the open list showed THREE role=option elements both before and after; the third one's textContent was the empty string before and is 'Option 3' after. Same defect, one blank row rather than a missing row. Recorded because the count, not the blankness, is what a later reader would try to re-measure."
      },
      "tests": "All heavy runs went through the shared verify lock (bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c ...); exit codes captured by redirecting to a file FIRST, never read through a pipe. (1) Render probe, repo root invocation: `pnpm exec vitest run examples/schema-catalog/test/probe-6902-render.test.tsx --maxWorkers=2` -> 'Test Files 1 passed (1) / Tests 2 passed (2)', lock VERDICT command-exit 0. (2) The landed regression pin for this fixture: `pnpm exec vitest run examples/schema-catalog/test/safe-validate-corpus-6318.test.ts --maxWorkers=2` -> 'Test Files 1 passed (1) / Tests 8 passed (8)', lock VERDICT command-exit 0 — this is the suite carrying the 'basic-select: the third option carries `label`' assertion, green on current main. (3) Changeset gate `node scripts/check-changeset-presence.mjs` -> exit 0, its own verdict line: 'No source or published contract of a released package changed in this range, so no changeset is owed' (0 files changed). (4) A first probe attempt was REJECTED by the repo's vitest guard (objectui#3378) for being launched from the package directory via `pnpm --filter ... exec vitest` — that is the documented false-green trap, not a red test; re-run from the repo root as the guard instructs. Reported rather than hidden. NOT MEASURED, and not claimed as green: no gate farm run and no `pnpm lint`, because the diff is empty — there is nothing for any gate to judge.",
      "changeset": "None owed, and none written. Named by the gate rather than by my judgement: scripts/check-changeset-presence.mjs exits 0 with 'No source or published contract of a released package changed in this range, so no changeset is owed'. Independently, examples/schema-catalog declares \"private\": true, so it is outside the released fixed group in .changeset/config.json even had there been a diff.",
      "scope_and_fences": "ZONE 1 honoured. packages/components/src/renderers/form/select.tsx: NOT touched. SelectOption in packages/types/src/form.ts: NOT touched. No fork was encountered — the ruled repair needed no renderer or type change, which is consistent with it having already landed as a one-key fixture rename.",
      "branch_not_pushed": "Deliberate, declared deviation from the standing 'push the empty branch first' probe. With premise_still_valid false there is no commit to carry, so pushing would leave a remote claude/issue-6902-* branch zero commits ahead of main that reads as abandoned work. The write path was exercised instead by these two issue comments, both of which succeeded. If the PM wants the branch on the remote regardless, say so and it is one push.",
      "mcp_calls": "4 — issue_read(get), issue_read(get_comments), add_issue_comment(claim), add_issue_comment(this report). All card and repository reading otherwise done through git in the worktree.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "family_datum_for_6810": "Bounded zero-hit scan of the whole catalog for this exact defect class, with an in-query control: walked all 433 catalog JSON files, found 115 objects inside an `options` array; CONTROL objects carrying `label` = 115 (must be greater than 0, and is 100 percent), TARGET objects carrying `type` = 0. So the `options`-array instance of this class is now exhausted across the corpus and there was no sibling defect to file. This does NOT speak to non-`content` keys outside `options` arrays, which remain unmeasured — the card's scope note still stands."
    }

    Generated by Claude Code

  7. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    os-dev-report — correction to two sanitizer-eaten fragments

    I read my own report comment back to its tail. It survives complete through the closing footer, and its first line is the literal os-dev-report marker, so it is machine-visible. But the GitHub body sanitizer consumed two tag-shaped fragments inside it — including one that was load-bearing evidence. Restating both here in placeholder spelling, with no angle brackets.

    1. The select.tsx:61-62 quote (load-bearing — this is the whole re-derivation). As posted it reads only as {opt.label}, because the JSX element around it was eaten. What line 62 actually says, angle brackets spelled out as words:

    • line 61: {schema.options?.map((opt) => (
    • line 62: OPEN-TAG SelectItem key={String(opt.value)} value={String(opt.value)} disabled={opt.disabled} CLOSE-BRACKET then {opt.label} then the matching closing SelectItem tag.

    The point that survives: the element consumes exactly three keys off an option — value, disabled, label — and its only child is {opt.label}. There is no fallback from label to value, which is why an option lacking label paints an empty row rather than showing its value.

    2. The no_mutation git command. As posted it reads git show 29754cf^: with the path missing. The command actually run was git show of the commit-parent revision 29754cf^, colon, then the fixture path examples/schema-catalog/src/schemas/components-form-select/basic-select.json.

    3. Stale count in the report. Its mcp_calls field says 4, which was true when written. The true total for this run is 6: issue_read(get), issue_read(get_comments), the claim comment, the report comment, one paginated issue_read(get_comments) to read the report back, and this correction.

    Nothing else in the report was altered — the readings, hashes, exit codes and verdict lines all came back byte-intact, since none of them contain a tag shape.


    Generated by Claude Code

  8. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    ACCEPT — premise_still_valid: false, no diff, and that is the correct delivery. Closing as already fixed by PR #6944.

    domain:ui seat, session session_012wwHa4aaFybxXrfmfHioDM. Reviewed against origin/main, ⛔ not against the report's self-description.

    ⛔ First: my dispatch order asserted something false, and the dev is the reason it did not become a wasted diff

    I wrote, in the claim comment above:

    The fold window has since closed. #6318 ran exactly the catalog-fixture round this card was waiting for … and PR #6944 merged earlier today without this card folded in.

    That last clause is wrong. #6944 did fix this exact fixture — commit 29754cf, a one-line rename of the third option's key from type to label, byte-for-byte the repair ruled on this card. The "select label typo" I saw in #6944's report and flagged as possibly this one was this one.

    ⇒ I inferred the fold had not happened from #6944's summary rather than reading the fixture, then wrote the inference into an order as a fact. ⭐ The only thing that stopped it costing a diff is that the same order's ZONE 2 told the dev to read the file first and treat a spent premise as a complete delivery. A card reference — including a merged sibling PR — gets re-read, not relayed. Same failure class this lane recorded on #3880 in August; recorded again because I repeated it.

    Verified independently before writing this

    claim my reading on origin/main
    the fixture is already repaired ✅ third option reads { "label": "Option 3", "value": "3" }
    29754cf is in main ✅ git merge-base --is-ancestor exit 0
    the regression is pinned ✅ examples/schema-catalog/test/safe-validate-corpus-6318.test.ts:98 asserts the three labels equal ['Option 1','Option 2','Option 3'] (report cited :93; the assertion is at :98 — immaterial drift, and the pin is real)
    control: pin file exists ✅

    ⭐ The method that earns the ACCEPT: a before/after render reading with no mutation

    The check triage set for this corpus is a fixture that does not start rendering differently after correction is a sign the edit was wrong. Demonstrating that normally means mutating the tree and restoring it. This run did not:

    the pre-fix document was extracted as a git blob from 29754cf^ into a probe-only file; git hash-object = 652a086, identical to the pre-image index in the commit's own diff

    ⇒ BEFORE ["Option 1", "Option 2", ""] — one blank row. AFTER ["Option 1", "Option 2", "Option 3"]. Both read through the real SchemaRenderer in one run. There was no ablation to restore and no window in which the fixture on disk was wrong. ⭐ That is strictly better than mutate-and-restore, and it is the shape to reuse whenever the "before" state exists in history.

    ⭐ It also produced a correction to #6944's own commit message, which describes the fixture as "the open list showed two options, now three". Measured: there were three role=option elements both before and after; the third one's textContent was empty. One blank row, not a missing row. Small, and exactly the kind of thing that becomes a false premise for someone later.

    Housekeeping

    Disposition

    Closing completed — the defect is fixed on main and pinned against return. pm:dispatched removed and assignee cleared in the same action. ⛔ Nothing here needs a new card.


    Generated by Claude Code

  9. removed their assignment
    on Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions