Skip to content

form, react, types, console: spec 18 refuses the view form's visibleOn and the page component's visibility (objectstack#23543 F4) — read visibleWhen only, and re-point the spec-shape couplings before the next spec bump #12654

Open

Description

@objectstack-fleet

Filing gate ④, a cross-repo child: the objectui layer of objectstack-ai/objectstack#23543 family F4. That family landed as objectstack-ai/objectstack#23836 (105902f10a, merged 2026-10-16T11:07Z).

Blocked-by: objectstack-ai/objectstack#23836

What spec 18 changed

The ruling, verbatim:

  1. F4 A: visibleOn (view) and visibility (page component) retire as keys: retiredKey tombstones naming visibleWhen with the full spec-property-retirement kit (ADR-0087 markers, baselines, forms, docs, pins); both entries retire in the same change; the three record / strict-exit suites re-point. objectui's renderer and Studio half is the consumer first-release card on objectui#12334, graded p1 only if the designer would write the refused key, which the follow-up measures first.

From the landed changeset (.changeset/23543-f4-visibility-alias-keys-retired.md):

  • visibleOn on a form field (view.form.sections[].fields[]) and on a form section, and visibility on a page component, are retiredKey() tombstones naming visibleWhen. tsc fails at a typed authoring site, and the parse refuses the key by name (defineStack, os validate, os build, the metadata save door's 422).
  • The parse-time fold is gone: FormFieldSchema, FormSectionSchema and PageComponentSchema are plain closed objects again, with no .transform pipe.
  • Stored metadata needs no edit: stored-row rehydration still replays both conversions, so a stored view or page row written with the old key loads with visibleWhen.

The objectui sites

Measured by the F4 dev at objectui main 63d00da3de and at objectstack's .objectui-sha pin daeb77fc84 (identical). The seat re-read the three couplings below at main 8614705c18; line numbers there are given where they moved.

  1. Readers of the old keys (they read a key a spec-18 body can no longer carry):
    • visibleOn, about 17 form-field and form-section renderer reads: components/src/renderers/form/form.tsx:1861,1969-1971,2101-2102,3076,3161; plugin-form/src/WizardForm.tsx:867-868; plugin-form/src/sectionFields.ts:162,376,386,471; apps/console/src/components/FormPage.tsx:790,843; app-shell/metadata-admin/SchemaForm.tsx:956,1076.
    • 6 SDUI-node reads: react/src/SchemaRenderer.tsx:365,1939-1940,2190; sdui-parser/src/validate.ts:150; cli/src/commands/check.ts:63. Plus 9 type declarations.
    • Page-component visibility: react/src/SchemaRenderer.tsx:1944-1945,365,2191, and the type SchemaRenderer.tsx:191 (visibility?: PageComponent['visibility'], re-read at 8614705c18).
  2. Spec-shape couplings that go red at objectui's next spec bump:
    • plugin-form/src/sectionFields.spec-parity.test.ts reads (SpecFormFieldSchema as any).in.shape (:145 at 8614705c18). The pipe is gone, so .in no longer exists.
    • types/src/__tests__/flat-props-refusal-10872.test.ts against types/src/zod/public-blocks.zod.ts's PAGE_COMPONENT_NODE_KEYS (:340 at 8614705c18). Not run by the F4 dev.
    • react/src/SchemaRenderer.expressions.test.tsx:38,171: a Pick of PageComponent's visibility.
  3. objectstack's own lockstep port: objectstack packages/sdui-parser's SDUI_BASE_PROPS (a port of objectui's BaseSchema) still lists visibleOn. It moves with this card.

What done looks like

  • Each reader reads visibleWhen only. ⛔ No consumer-side fold of the old key back onto visibleWhen: the spec refuses it at every parsing door, and a renderer tolerance would hide an authored mistake.
  • The three couplings are re-pointed at the spec-18 shape before the bump that brings @objectstack/spec 18 lands, so the bump PR does not go red on them.
  • The Designer / Studio writes no retired key. The F4 dev measured none; re-check at the bump.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, enhancement · priority:p2 · target:v18 · domain:ui · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T11:58Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • The body's Blocked-by: is satisfied. feat(spec)!: retire the view form's visibleOn and the page component's visibility as keys; their ADR-0089 renames leave the load path (F4 of #23543) objectstack#23836 merged at 2026-10-16T11:07Z (105902f10a). The readers' change does not wait for the spec bump: a served row reaches objectui already folded to visibleWhen, by next.2's parse fold and by spec 18's stored-row rehydration alike.
    • Why p2, not p1: the ruling (6182001882, item 4) makes this card p1 "only if the designer would write the refused key". The F4 dev measured 0 such writes, so the condition reads no. No user-visible behaviour is wrong today.
    • Why target:v18: the ruling names this the consumer first-release card of a v18 retirement.
    • Order the claimant keeps: the three spec-shape couplings (sectionFields.spec-parity.test.ts's .in.shape, flat-props-refusal-10872.test.ts, and SchemaRenderer.expressions.test.tsx's Pick) must not go red against the installed next.2. Re-point them so they hold on both shapes, or move them in the bump PR that brings spec 18 if that comes first.
    • Not this card's surface: objectstack's packages/sdui-parser SDUI_BASE_PROPS (validate.ts:150, still listing visibleOn on objectstack origin/main) is another repository. sdui-parser is domain:devx by objectstack's domain table, so it moves in the objectstack pin-move PR (domain:devx) that carries .objectui-sha past this card. (Corrected by this seat in its next round: the first version named objectstack#23543's domain:spec seat.)
  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p2, target:v18; the objectui consumer half of objectstack-ai/objectstack#23543 F4, letter A, as triage graded it at 6214999676)
    Session: session_011K9g23W67dy2zwnyzaoB6o
    Account: os-tesla
    Branch: claude/issue-12654-visiblewhen-only
    Worktree: objectui-issue-12654 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the readers of the two retired keys, visibleOn (the spec view's form field and section) and visibility (the spec page component), and the three spec-shape couplings triage names:

    • packages/components/src/renderers/form/form.tsx (the visibleOn reads at :1861-1887 and :1969-1976);
    • packages/react/src/SchemaRenderer.tsx (visibility, :191) and packages/types/src/** wherever either retired key is declared or folded;
    • apps/console/src/components/FormPage.tsx (:790, :843) and packages/app-shell/src/views/RecordFormPage.tsx;
    • packages/cli/src/commands/check.ts (:63) only if the census shows it lists the spec view's key;
    • the couplings packages/plugin-form/src/sectionFields.spec-parity.test.ts, packages/types/src/__tests__/flat-props-refusal-10872.test.ts and packages/react/src/__tests__/SchemaRenderer.expressions.test.tsx, re-pointed so they hold against the installed next.2 and against spec 18;
    • tests beside each, and changesets for the touched packages.

    Outside the surface: the homonyms that are not the spec keys (CommandPalette's command-item visibleOn; the metadata-admin designer's own SchemaForm field visibility in form-spec.ts, SchemaForm.tsx and anchors.ts), unless the census proves one reads the spec view's key; objectstack's sdui-parser (SDUI_BASE_PROPS, objectstack-ai/objectstack#23543's family). A route outside this surface stops and reports (stop on breach; explain in the report).
    Container & model: M, mode:cloud, model: opus (default tier: separating the homonyms and re-pointing the couplings are judgment calls; dispatch-gates --tier --repo objectstack-ai/objectui over the 7 planned paths: no path-derived mandate)
    Clause-②: no
    Clause-② reason: narrowing. The published readers in @object-ui/components, @object-ui/react and @object-ui/types stop honouring two keys the spec retired; a served row already reaches objectui folded to visibleWhen (next.2's parse fold, spec 18's rehydration), but a host handing a raw unparsed schema loses them. So the card owes one CONTRACT_REVIEW_TIER review before ready, judged from the content.
    Responsibility: n/a — not a defect card
    Thread-read: 6214999676
    Serial constraints cleared: read at objectui main db8ce2f41d.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12654,
      "status": "done",
      "branch": "claude/issue-12654-visiblewhen-only",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12692",
      "session": "session_01SdA7b5ZrdDDBDKG48XRqy7 — cloud dispatch; this run's harness-stamped id (commit Claude-Session trailer)",
      "premise_still_valid": true,
      "summary": "The card's premise holds: the spec retired form-view `visibleOn` and page-component `visibility`, and objectui readers still folded them back. The census partly disproved H1. `form.tsx` reads objectui's runtime FormField `visibleOn` slot (the view-level gate that `sectionFields.ts` fills), not the spec key, so it is unchanged. The real spec-key readers were: console `FormPage` buildSections (field and section `?? visibleOn`); plugin-form `normalizeSectionField` (`fd.visibleWhen ?? fd.visibleOn`, which is outside the claim surface and fixed in place under role rule 3's bounded exemption, see open_questions); and `SchemaRenderer`'s `visibility` leg, type member and prop strip. All three now read `visibleWhen` only. The three couplings are re-pointed: the spec-parity test walks to the shape through a pipe if there is one, and its `visibleOn` row now asserts the key is dropped (the tombstone keeps the key in the shape on both lines). The expressions test's probe declares `visibility` itself instead of picking it off `PageComponent`, whose 18 type is never. The 10872 test already walked the pipe and held unchanged; `PAGE_COMPONENT_NODE_KEYS` keeps `visibility` because spec 18 still carries it as a key. H2: next.2's `PageComponent['visibility']` is the live deprecated `EvaluatedExpressionInput`; spec 18 source has `retiredKey` (never). H3: `@object-ui/types` declares neither spec key as live (`BaseSchema.visibleOn` is objectui's own; `form.zod` `visibleOn` is the runtime slot), so types is unchanged. H4: `check.ts` lists BaseSchema vocabulary, not the spec view key, so it is unchanged. H5 is pinned in four places (two FormPage tests, sectionFields.test, the expressions test), each pairing the retired key gating nothing with the same predicate under `visibleWhen` gating. Docs: `plugin-form.mdx` (runtime `visibleOn` row) and `metadata-diagnostics.md` (page component `visibility`) updated. Census of every non-test `visibleOn` hit, with its verdict: form.tsx 1861/1969/2101/3076/3161 = runtime slot, keep; WizardForm 867 = runtime slot, keep; sectionFields 162/169/386 = runtime slot, keep; sectionFields 471 = spec field key, CHANGED; sectionFields 376 = object FieldSchema meta, undeclared key, finding; FormPage 790/843 = spec key, CHANGED; RecordFormPage 297 = comment only (its sections flow into sectionFields); SchemaForm 956/1076 + form-spec.ts + anchors.ts + view-create-body = metadata-admin designer, mixed (finding); CommandPalette 145 = command item, homonym; SchemaRenderer 365/1939 + base.ts 437 + base.zod 489 + sdui-parser 150 + cli check 63 + core schema-builder 68 + objectql.ts 2503 + objectql.zod 1192 = BaseSchema's own deprecated key, not the spec's (the spec's PageComponentSchema never declared `visibleOn`), keep; types form.ts 1852 / form.zod 1080 = runtime slot, keep. Worktree `../objectui-issue-12654` is left in place because no PR is open yet (cleanup rule 4 runs after the PR opens). The card's assignee was not touched.",
      "tests": "All tests ran from the repo root with the paths relative to it, on a tree equal to HEAD 0940280; the follow-up 0241923 changes only changeset text. react: 16 files naming visibleOn/visibility/visibleWhen → 16 passed, 269 tests passed. plugin-form: 25 files → 25 passed, 385 passed + 1 skipped. console FormPage*.test.* + components tests naming visibleOn: 33 files → 318 passed. types/sdui-parser/core tests naming the keys + flat-props-refusal-10872: 20 files → 1299 passed. app-shell tests naming visibleOn + RecordFormPage tests: 14 files → 253 passed. Ablations used objectstack scripts/ablation-replace.mjs, with a trap on HEAD restore and blob==HEAD proven each time. The tests import the source relatively, so no dist preflight applies. (1) restore `?? fd.visibleOn` in sectionFields.ts: anchor 1→0, blob 92485f6→8636ed4, 2 failed (spec-parity `visibleOn` row, sectionFields retired-key pin); restored blob 92485f6 == HEAD. (2a) restore `?? override.visibleOn` in FormPage: 2 failed / 34; (2b) `?? sec.visibleOn`: 2 failed / 34; restored blob fbee1fe == HEAD. (3) restore the `visibility` leg in SchemaRenderer: the FIRST attempt was a no-op, refused by the tool because the replacement contained the anchor (count 1→1) and the test never ran; re-run with a consumed anchor: anchor 1→0, blob 74db941→cf5cf3d, 1 failed / 17 (`does not read the retired visibility key`); restored blob 74db941 == HEAD. Spec-18 leg of the couplings: NOT MEASURED, reason: spec 18 is not installed (installed 18.0.0-next.2). The shape walks hold for a plain closed object by construction, and spec-18 source keeps both keys as `retiredKey` shape members.",
      "gates": "Local, exit codes captured before any pipe. Closure build `turbo run build --filter=@object-ui/plugin-form... --filter=@object-ui/react...`: EXIT 0, 12/12 tasks. `turbo run build --filter=@object-ui/console...`: EXIT 0, 35/35. type-check: @object-ui/react EXIT 0 (tsc + tsconfig.test.json), @object-ui/plugin-form EXIT 0 (tsc + tsconfig.test.json), @object-ui/console EXIT 0. pnpm check:changeset-claims 0 (re-run at 0241923: 0); changeset:check 0 (re-run 0); check:test-path-roots 0; check:spec-symbols 0; check:new-line-citations 0; check:control-bytes 0; check:doc-types 0; check:doc-fences 0; check:doc-json 0 (after the types build); check:doc-examples 0 and check:doc-snippets 0 (after the scoped 34-package build); check:doc-example-readers 0; check:handler-key-reads 0; check:pending-changeset-literals 0; check:metadata-write-doors 0. node scripts/check-changeset-presence.mjs 0. `node scripts/check-nul-bytes.mjs` = MODULE_NOT_FOUND: the script exists in objectstack only, so this is not a gate reading; check:control-bytes is objectui's counterpart and is green. eslint --no-inline-config on the 16 changed ts/tsx files: EXIT 1 with 2 errors, both present on the base f78efdd (measured with eslint --stdin on the base blob): FormPage 'Cannot create components during render' (base line 1834) and spec-parity's restricted FormFieldSchema import (carries an inline disable that --no-inline-config ignores). None is introduced. CI families: no PR is open, so there are no check-runs to cite; in_progress. Repo-wide `pnpm lint` and the full test shards belong to CI. dispatch-gates.mjs --commands/--ran: not available for objectui (objectstack-only), so the list above was hand-derived from objectui package.json + ci.yml. The test:repo project was not run (diff touches none of its files).",
      "mcp_calls": "2 — mcp__github__issue_read (get, get_comments); read-only",
      "api_writes": "0 — the only GitHub writes are 2 `git push` calls to the branch (plus the empty-branch probe push)",
      "open_questions": [
        {
          "question": "The real spec-field reader `packages/plugin-form/src/sectionFields.ts` (`fd.visibleWhen ?? fd.visibleOn`) is outside the claim's file surface. The claim says 'stop on breach'; os-dev rule 3 grants a bounded in-place fix when four conditions hold: same defect class; a mechanical one-expression fix; no other claim holds the file; the same gate family, since the claim already names this directory's spec-parity test. Which binds?",
          "options": [
            "A Keep the in-place fix (as pushed); the seat adds sectionFields.ts and its two sibling tests to the claim's file surface and names the fix in the PR body (done)",
            "B Revert the sectionFields.ts hunk and its pins; file a follow-up card for the plugin-form reader"
          ],
          "recommendation": "A. All four rule-3 conditions hold. Under B, `Fixes #12654` would close the card while the record form and ObjectForm still honour the retired key. The card's done-criterion is 'each reader reads visibleWhen only', and the four-axis frame rules out a staged tolerance window."
        }
      ],
      "out_of_scope_findings": [
        "class: b · reach: named producer — app-shell metadata-admin's own create/anchor schemas (`anchors.ts` viewKind sections, `view-create-body.ts`) WRITE `visibleOn` into designer layouts, and `SchemaForm`'s `readVisibility` reads `visibleWhen ?? visibleOn` over both those and spec-served FormViews, so the designer keeps the retired spelling as a live internal dialect. The F4 measure (0 writes into saved metadata) still holds: these are in-app TS layouts, not saved rows. · evidence: SchemaForm.tsx `readVisibility`, form-spec.ts `FormSectionSpec.visibleOn` / `FormFieldSpec.visibleOn`, anchors.ts `visibleOn: \"data.viewKind == 'list'\"` · dedupe words: metadata-admin SchemaForm readVisibility visibleOn designer create schema anchors · carrier: none — seat to file",
        "class: c · reach: named producer — none known; `normalizeSectionField` string shorthand attaches `objectSchema.fields[name].visibleOn` (sectionFields.ts `meta?.visibleOn`), a key the spec object FieldSchema does not declare. Undeclared-key read, dormant. · dedupe words: sectionFields meta.visibleOn object field undeclared fromObjectSchema · carrier: none — noted in the PR's Acceptance notes",
        "class: n/a · `BaseSchema.visibleOn` (objectui's own deprecated ADR-0089 node key: SchemaRenderer leg, sdui-parser SDUI_BASE_PROPS, cli check structural keys, core SchemaBuilder.visibleOn(), docs guide/expressions.md and schema-rendering.md) is a different key from the spec's retired ones and is not this card. Whether objectui retires its own alias is a separate decision. · carrier: none — Acceptance notes only"
      ]
    }

    Posted by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The seat filled pr with the draft PR it opened, #12692. On the open question, the seat takes A: sectionFields.ts and its two sibling tests join the claim's surface, since the claim already names that directory's spec-parity coupling and the card's done-criterion covers every reader.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report (delta, REWORK round 1 on PR #12692)

    {
      "issue": 12654,
      "status": "done",
      "branch": "claude/issue-12654-visiblewhen-only",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12692",
      "head": "e8099b7",
      "summary": "Rework round 1. (1) packages/components/src/__tests__/page-variables.test.tsx re-pointed: 'also gates on the spec-canonical `visibleWhen` predicate' (renderGated now accepts visibleWhen) + 'does not gate on the retired `visibility` key'; the envelope case is split into 'gates on a `visibleWhen` Expression envelope { dialect, source }' (new; envelope gating coverage kept) + 'does not gate on a `visibility` Expression envelope — the retired key'. The no-DOM-leak case is unchanged and green. (2) scripts/__tests__/check-doc-expression-carriage.test.ts counts now match what the gate derives: VISIBILITY_* arrays 6→5, channels.conditions 8→7 (derived list: disabled, disabledOn, hidden, hiddenOn, visible, visibleOn, visibleWhen). `node scripts/check-doc-expression-carriage.mjs` is report-only and exited 0. Its one finding is content/docs/api/schema-reference.md `action.condition`, unrelated to visibility; no doc page carries ${…} under `visibility`, so no doc change. (3) Settled the remaining `visibility`/`visibleOn` pins in both test trees: the rest are other keys (marketplace package `visibility`; `page:tabs` item `visibility`, read by PageTabsRenderer; types flat-arm UNDECLARED_ENVELOPE, still green). (4) Merged origin/main 023087f with a real merge commit (e8099b7, 2 parents, no conflicts; incoming changes touch no visibility/visibleOn reader). Correction to last round's deviation: test:repo-family tests (scripts/__tests__) read the renderer source by path, so 'diff touches none of its files' was the wrong criterion.",
      "tests": "Pre-fix, at 0241923: page-variables + carriage test → 3 failed / 70 (both page-variables cases plus 'agrees with the renderer's own VISIBILITY_* declarations'), reproducing CI. After the fix, before the merge: `pnpm exec vitest run packages/components/src/__tests__` → 217 files passed (1 skipped), 2190 tests passed (7 skipped), EXIT 0; `pnpm exec vitest run scripts/__tests__` → 186 files passed (2 skipped), 5619 tests passed (2 skipped), EXIT 0. On the merged head e8099b7: the targeted union (react/plugin-form tests naming the keys, console FormPage*.test.*, page-variables, the carriage test, flat-props-refusal-10872, page-component-view-data-12666) → 74 files, 1520 passed (1 skipped), EXIT 0. Ablations of the re-pinned cases, using objectstack scripts/ablation-replace.mjs with a trap restoring from HEAD: (A) visibility leg restored in SchemaRenderer.tsx: anchor 1→0, blob 74db941→cf5cf3d, page-variables 2 failed / 13 ('does not gate on the retired `visibility` key', 'does not gate on a `visibility` Expression envelope'). (B) 'visibility' restored in VISIBILITY_SHOW_KEYS: anchor 1→0, blob 74db941→58115bd, carriage test 1 failed / 59 ('agrees with the renderer's own VISIBILITY_* declarations'). After both: restored blob 74db941 == HEAD:packages/react/src/SchemaRenderer.tsx, git diff HEAD empty.",
      "gates": "On merged head e8099b7: turbo build --filter=@object-ui/console... EXIT 0; type-check @object-ui/react 0, @object-ui/plugin-form 0, @object-ui/console 0; pnpm check:changeset-claims 0, changeset:check 0, check:test-path-roots 0, check:spec-symbols 0, check:new-line-citations 0, check:control-bytes 0; node scripts/check-doc-expression-carriage.mjs 0 (report-only; run before the merge, and the merge touches neither SchemaRenderer nor the carriage script). CI check-runs on e8099b7: not yet reported (in_progress).",
      "mcp_calls": "0 this round",
      "api_writes": "0 — the only GitHub write was 1 `git push` (0241923..e8099b7)"
    }

    Posted by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The PR body's local-gates lines were refreshed from the same message, and its Clause-② line now reads Clause-②: no (narrowing). Next: CI on e8099b7, then the CONTRACT_REVIEW_TIER review this narrowing owes.


    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 seatenhancementNew feature or requestpm:dispatchedpriority:p2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions