Skip to content

fix(app-shell): the metadata-admin designer reads and writes visibleWhen only (objectui#12696) - #12770

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12696-designer-visiblewhen
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12696-designer-visiblewhen

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12696
Clause-②: no (narrowing)

Summary

The metadata-admin designer was the last reader and writer of the form view's retired visibleOn predicate. objectui#12654 (PR #12692) had already removed it from the console form page, plugin-form and SchemaRenderer. After this PR the designer reads and writes visibleWhen only:

  • Writer. anchors.ts writes visibleWhen: "data.viewKind == 'list'" / 'form' on the view create schema's kind and formType pickers.
  • Reader. SchemaForm's readVisibility returns node?.visibleWhen and nothing else. The ?? visibleOn fold is gone from all five call sites: flat create properties, sections, section fields, tab reachability and record-editor sub-fields.
  • Types. FormFieldSpec and FormSectionSpec (exported from the @object-ui/app-shell entry) drop their visibleOn member. 'visibleOn' stays in FormSectionSpec's Omit list, because the spec's input FormSection type still carries it and would otherwise re-admit it.
  • Condition widget. CONDITION_FIELD_NAMES drops 'visibleOn'. A *When property still gets the condition builder by name.
  • Prose. Doc comments that called the predicate visibleOn now say visibleWhen, in predicate.ts, view-schema.ts, page-schema.ts, report-schema.ts, mergeServerFields.ts, PageDefaultInspector.tsx, ViewVariantInspector.tsx and ReportDefaultInspector.tsx. The last one is included because objectui#12745 merged before this branch's origin/main merge.

The narrowing (what a host sees)

A host that passes visibleOn to SchemaForm, on a FormView section, a section field, a record sub-field or a raw JSONSchema property, now sees that node shown unconditionally. This matches the runtime form. A typed object literal that sets visibleOn on a FormFieldSpec or FormSectionSpec no longer compiles (TS2353). The fix is one rename: write visibleWhen. The predicate value keeps the same shape. The changeset says the same.

Measurements behind each step

  • No producer left. After the anchor change, no non-test file in packages/ or apps/ writes visibleOn into a FormView or a create schema that SchemaForm reads. The 17 authoring FormViews the bundled spec exports (viewForm, pageForm, reportForm, objectForm, fieldForm, actionForm and others) carry 0 visibleOn nodes and 89 visibleWhen nodes.
  • Condition widget entry. No bundled authoring form declares a field named visibleOn, while visible and visibleWhen do appear (the positive control). The spec's JSON schemas still describe a deprecated visibleOn property on FormSection / FormField, but no designer form surfaces that position. So the entry had no named producer and is dropped.
  • Spec version. The installed @objectstack/spec 18.0.0-next.2 still accepts visibleOn as a deprecated alias that it folds into visibleWhen at parse. objectstack main turns it into a retiredKey. Either way, a parsed form reaching the designer carries visibleWhen.

Tests

  • view-create-body.visibility.test.tsx (new) renders the real registered view create schema and flips viewKind. Each picker shows only for its own family.
  • The SchemaForm.visibleWhen.test.tsx suite named "retired visibleOn is not read (objectui#12696)" replaces the old alias suite. A section and a field carrying only visibleOn are not hidden. The same predicates under visibleWhen are hidden. A raw create property carrying only visibleOn is not hidden.
  • view-create-body.test.ts pins visibleWhen on both pickers and the absence of visibleOn.
  • form-spec.containers.test.tsx PIN J refuses visibleOn on both types at compile time.
  • The alias-spelled fixtures in the flat-visibility, page-visibility and collapsible suites are re-spelled visibleWhen; they only used the alias.
  • Ablations, each with its restore proven by blob equal to HEAD:
    • Re-spelling the anchor's list gate visibleOn makes 2 cases fail.
    • Re-adding the reader's fold makes the 2 retired-key cases fail.
    • Re-adding visibleOn on FormSectionSpec makes tsc -p tsconfig.test.json fail with exactly one TS2578, at PIN J.
  • Runs at the head this PR opens on:
    • The metadata-admin directory: 517 files, 5805 passed, 1 skipped.
    • Every other test file naming visibleOn, readVisibility, FormFieldSpec or FormSectionSpec: 40 files, 889 passed, 1 skipped.
    • @object-ui/app-shell and @object-ui/console type-check: both green.

Outside the claimed file surface (forced by the narrowing)

Acceptance notes

  • Stale visibleOn prose left outside this surface:
  • None of them is filed.
  • The spec skew (deprecated alias in 18.0.0-next.2, retiredKey on objectstack main) closes with the next spec bump.

Provenance: built by the cloud session session_011Nr8s6FN4wcgqMHMM7oZk4 (claim 6221456246) and opened by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) from that session's final message, because a cloud session cannot open a PR from its container. The seat replaced the dev's own footer line with this line.


Generated by Claude Code

…hen only

The view anchor's create schema wrote the retired form-view `visibleOn`
on its `kind` / `formType` pickers, and SchemaForm's `readVisibility`
folded it back in with `visibleWhen ?? visibleOn` over every FormView
section, field and raw create property. Both halves now speak
`visibleWhen` only: the anchor writes it, the reader drops the fold,
`FormFieldSpec` / `FormSectionSpec` drop their `visibleOn` member, and
`CONDITION_FIELD_NAMES` drops the name (no bundled authoring form
declares a property called `visibleOn`). Doc-comment prose that called
the predicate `visibleOn` is re-spelled.

Claude-Session: https://claude.ai/code/session_011Nr8s6FN4wcgqMHMM7oZk4
Co-authored-by: Claude <noreply@anthropic.com>
…ibleWhen

objectui#12745 has merged, so the last doc comment that called the
form-view predicate `visibleOn` is re-spelled with the others.

Claude-Session: https://claude.ai/code/session_011Nr8s6FN4wcgqMHMM7oZk4
Co-authored-by: Claude <noreply@anthropic.com>
…them

`FormSectionSpec` / `FormFieldSpec` no longer declare `visibleOn`, so the
objectui#12654 pins that hand `buildSections` a raw section and a raw field
carrying it now mark that literal with `@ts-expect-error`. The runtime
assertion (the retired spelling reaches no row) is unchanged.

Claude-Session: https://claude.ai/code/session_011Nr8s6FN4wcgqMHMM7oZk4
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 74 chunks) 3126.0 KB 3744.1 KB
Main entry chunk (gzip) 75.6 KB 350 KB
Entry file index-C1JziXaW.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.42KB 7.85KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
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) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 600.51KB 145.04KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.96KB 69.45KB
fields (index.js) 291.36KB 74.46KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.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.75KB
mobile (index.js) 1.99KB 0.87KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.63KB 69.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.28KB 46.60KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 270.35KB 74.86KB
plugin-kanban (index.js) 59.42KB 18.48KB
plugin-list (index.js) 132.94KB 34.07KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.23KB 39.46KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 875c7614cd5990f233c2a52326fb6c75c5b38ccc
Local-runs: none
Scope: .changeset/12696-designer-visiblewhen.md, apps/console/src/components/FormPage.conditionalRules.test.tsx, apps/console/src/components/FormPage.fieldSpec.test.ts, apps/console/src/components/FormPage.viewSpec.test.ts, apps/console/src/components/FormPage.visibleWhen.test.tsx, packages/app-shell/src/views/metadata-admin/ConditionWidget.conditionScope.test.tsx, packages/app-shell/src/views/metadata-admin/SchemaForm.collapsible.test.tsx, packages/app-shell/src/views/metadata-admin/SchemaForm.flatVisibility.test.tsx, packages/app-shell/src/views/metadata-admin/SchemaForm.pageVisibility.test.tsx, packages/app-shell/src/views/metadata-admin/SchemaForm.tsx, packages/app-shell/src/views/metadata-admin/SchemaForm.visibleWhen.test.tsx, packages/app-shell/src/views/metadata-admin/anchors.ts, packages/app-shell/src/views/metadata-admin/form-spec.containers.test.tsx, packages/app-shell/src/views/metadata-admin/form-spec.ts, packages/app-shell/src/views/metadata-admin/predicate.test.ts, packages/app-shell/src/views/metadata-admin/view-create-body.test.ts, packages/app-shell/src/views/metadata-admin/view-create-body.visibility.test.tsx + apps/console/src/components/FormPage.tsx, apps/console/tsconfig.json, packages/app-shell/src/index.ts, packages/app-shell/src/views/metadata-admin/index.ts, packages/app-shell/src/views/metadata-admin/registry.ts, packages/app-shell/src/views/metadata-admin/useMetadata.ts, packages/app-shell/src/views/metadata-admin/mergeServerFields.ts, packages/app-shell/src/views/metadata-admin/previews/block-config.ts, packages/app-shell/src/views/RecordFormPage.tsx, packages/app-shell/src/chrome/CommandPalette.tsx, packages/app-shell/tsconfig.json, packages/app-shell/tsconfig.test.json, packages/plugin-form/src/sectionFields.ts, packages/components/src/renderers/form/form.tsx, packages/react/src/SchemaRenderer.tsx, packages/types/src/form.ts, apps/console/README.md, packages/plugin-form/README.md, .changeset/config.json, .changeset/pre.json, scripts/check-changeset-no-major.mjs, .github/workflows/changeset-guard.yml; in @objectstack/spec at the 18.0.0-next.2 tag (objectstack commit e5136ff10f1a0196d3ff4d971338a59506652297): packages/spec/src/ui/view.zod.ts, packages/spec/src/shared/visibility.ts, packages/spec/src/system/metadata-form-registry.ts, the 17 packages/spec/src/**/*.form.ts, packages/spec/src/ui/component.zod.ts, packages/spec/src/data/object.zod.ts, packages/spec/src/ui/app.zod.ts; and packages/spec/src/ui/view.zod.ts on objectstack main fd0e72abc2af5bd4d7056e7c2e387dc992d0f1df.

Face: narrowing. reviewFace over the PR's 26-path file list with the PR body and the committed changeset handed as the declaration answers the narrowing face with no schema hit and no governed hit; readClause2Line reads declared · no · narrowing on the PR body's second line and on the changeset's Clause-②: no (narrowing). One member is removed … line. Prose faces, proven prose-only by the diff: every hunk in predicate.ts, view-schema.ts, page-schema.ts, report-schema.ts, mergeServerFields.ts, conditionScope.ts, PageDefaultInspector.tsx, ViewVariantInspector.tsx and ReportDefaultInspector.tsx changes one word, visibleOn to visibleWhen, on a line inside a doc comment; no executable token moves in any of the nine.

Check-runs on this head, read after the last one completed at 2026-10-16T19:25Z: 36 check-runs, 33 success, 3 skipped (Test (coverage), its shard matrix row, dependabot), 0 failure, 0 pending. Green by name: Type Check, Lint, Repo Gates, Test and its 16 shards, Test (dist pins), Build and E2E, Build Docs, Bundle Analysis, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report, Governed Surface Queue Guard, Line Citation Gate, Live E2E (informational), label.

① Derived judgments

  1. Accept set — SchemaForm stops honouring visibleOn on every node it reads: RIGHT. readVisibility now returns node?.visibleWhen and nothing else, and its five call sites are the only predicate reads in the file: the flat create-schema property filter (readVisibility(props[k] as any)), the section filter, the section-field filter, the tabbed-form reachability filter and the record-editor sub-field filter. The only remaining visibleOn text in SchemaForm.tsx is the function's doc comment. Pinned in both directions by the new suite "retired visibleOn is not read (objectui#12696)": a section and a field carrying only a visibleOn that would hide them are shown, the same predicates under visibleWhen hide them, and a raw create property carrying only visibleOn is shown; the "visibleWhen wins over visibleOn" case is kept. The dev's A2 ablation (fold re-added, two cases fail) agrees with the read.

  2. Producers — zero in-repo, and the spec-served authoring forms carry none: RIGHT, with one stated exposure. A full non-test census of visibleOn over packages/ and apps/ at this head finds the two anchors.ts writes gone (kind and formType now carry visibleWhen: "data.viewKind == 'list'" / 'form') and every other hit to be a comment or a DIFFERENT key: BaseSchema.visibleOn (SchemaRenderer, @object-ui/types base.ts), the runtime FormField.visibleOn slot (@object-ui/types form.ts, components form.tsx, plugin-form sectionFields.ts and WizardForm.tsx — the slot 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 kept), the navigation item's visibleOn (CommandPalette, item.visible ?? item.visibleOn) and the CLI check's key list. None is a FormView or create schema SchemaForm reads. For spec-served forms: the runtime's /meta/types form is the spec's metadata-form-registry (view: viewForm and siblings), so a served authoring form IS a bundled spec form. At the installed @objectstack/spec 18.0.0-next.2 the 17 *.form.ts files carry 0 visibleOn tokens; visibleWhen tokens: field 52, object 34, action 13, view 8, page 6, report 4, the other eleven 0 (token hits, so the dev's 89 node count is not the same unit; the two readings agree on the zero that matters). Exposure stated rather than hidden: useMetadata.ts says the served form is deserialised without validation, so a runtime older than ADR-0089's fold could in principle serve visibleOn; the designer's dependency floor is ^18.0.0-next.2, the spec has folded visibleOn at parse since protocol 15 (normalizeVisibleWhen), and 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's landed changesets accepted the same exposure for the console form page. Judged right.

  3. Public surface — FormFieldSpec and FormSectionSpec lose visibleOn: RIGHT. Both are re-exported from the @object-ui/app-shell entry (export type { FormFieldSpec, FormSectionSpec, FormViewSpec } through views/metadata-admin/index.ts). The reader census the dev posted was re-derived from the head's importer list: apps/console FormPage.tsx types RenderableField.visibleWhen off FormFieldSpec['visibleWhen'] and reads visibleWhen only; PackageFormDialog, widgets, useMetadata, field-source-label, mergeServerFields and the CodeWidget tests touch neither key; metadata-viewer.tsx names FormFieldSpec.options in a comment only. Nothing in-repo reads .visibleOn off either type.

  4. Keeping 'visibleOn' in FormSectionSpec's Omit list: RIGHT and load-bearing. At the tag, the exported FormSection is declared as the z.input type OF FormSectionSchema (generics are spelled in words here because the comment sanitizer eats tag-shaped text), and FormSectionSchema declares visibleOn: EvaluatedExpressionInputSchema.optional() marked [DEPRECATED → visibleWhen], so the input type carries the key and the intersection would re-admit it without the Omit. PIN A (an Equal of the two Omit types over SectionNarrowed, one of FormSectionSpec and one of FormSection) needs 'visibleOn' in SectionNarrowed for the same reason, and PIN J's @ts-expect-error on a FormSectionSpec literal is what turns to TS2578 if the Omit entry goes (the dev's A3). FormFieldSpec is hand-declared, and the spec's own FormField type is already Omit of visibleOn, so the field leaf has no such position. On objectstack main both spec positions are retiredKey(...) tombstones (z.never().optional()): the key is still in the input type there, so the Omit stays right after the next spec bump too.

  5. The two @ts-expect-error lines in apps/console keep their tests' runtime assertions meaningful: RIGHT. apps/console/tsconfig.json includes src with no test exclusion and the package's type-check is tsc --noEmit, so the directives are compiled, not phantom (the dev measured TS2353 at both lines before adding them; an unused directive would have failed Type Check as TS2578, and Type Check is green). Each directive covers the one-object line only; the visibleOn section and field stay in the arrays at runtime, and the assertions (undefined in the row for c) are unchanged — they still read what buildSections does with a raw retired key. PIN J in form-spec.containers.test.tsx is compiled by tsconfig.test.json, chained from app-shell's type-check.

  6. CONDITION_FIELD_NAMES drops 'visibleOn': RIGHT; no reachable position loses the builder. detectConditionWidget(name, schema) keys on a property NAME (the set, or /When$/). Served JSON schemas that literally declare a property named visibleOn at the tag: FormField and FormSection in view.zod.ts, deprecated — the only two; the visibleOn entries in component.zod.ts (object-form block), object.zod.ts (field groups) and app.zod.ts (areas) are guidance prescriptions for an unknown key, not properties. Designer reach to those two positions: no bundled form declares a field named visibleOn (0 across the 17; positive controls { field: 'visible' } in the action form and { field: 'visibleWhen' } in the field and object forms exist); viewForm has no sections or config field, so derivePropertyNames never enumerates a FormSection or FormField item schema; the page block inspector's record:details.sections uses a hand-listed itemFields (name, label, columns, fields); mergeServerFields appends only TOP-LEVEL server-only properties, and no served type schema names a top-level visibleOn. Any *When name keeps the widget. And a property spelled with the retired key should not be offered a builder.

  7. Tests re-spelled, not deleted: RIGHT. The collapsible, flat-visibility and page-visibility fixtures only ever pinned the alias limb; re-spelling them visibleWhen keeps every assertion and moves it onto the key a current backend serves. view-create-body.test.ts pins visibleWhen on both pickers and not.toHaveProperty('visibleOn'); the new view-create-body.visibility.test.tsx renders the REAL registered create schema through registerBuiltinAnchors and getMetadataResource('view').createSchema and flips viewKind (the dev's A1: two cases fail if the anchor regresses). The pageForm reachability case now asserts not.toHaveProperty('visibleOn') rather than toBeUndefined, a stricter read.

② Semver level

  • The changeset bumps @object-ui/app-shell minor, opens with BREAKING for a host that still spells visibleOn on a layout handed to SchemaForm, states the FROM → TO mapping and the one-line fix (write visibleWhen; the predicate value keeps its shape), and carries Clause-②: no (narrowing) — read as declared · no · narrowing, matching the PR body's line 2.
  • This repo's policy: .changeset/config.json holds one fixed group of 40 packages (app-shell and console in it) and .changeset/pre.json is in pre mode, tag next; AGENTS.md §版本号策略 forbids major (the gate scripts/check-changeset-no-major.mjs, run by changeset-guard.yml as the check-run Changeset Bump Policy: success) and spells objectui's own breaking changes as minor with the break stated in the body. minor is therefore the right level. Changeset Declaration (presence) and Changeset Fixed Group Check: success. objectui runs no ADR-0087 disposition gate, so none is owed here.
  • What publishes matches the declaration: the only non-test source that moves is in packages/app-shell; the apps/console hunks are four test files, so no console source changes, and the fixed group carries console with the app-shell bump.
  • Wording note, not a defect: the changeset says @objectstack/spec 18 retires the form view's visibleOn. At the installed 18.0.0-next.2 the key is a deprecated alias folded at parse; the retiredKey tombstones are on objectstack main and not yet in a spec objectui installs. 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's landed changesets use the same phrasing, and the host-facing instruction (write visibleWhen) is right under both versions.

③ Boundary flags

  • Q1 — surface extension into four apps/console tests (seat answered A): accepted. The edits are forced by the type narrowing the card asks for, every one is in a test (two drop a visibleOn line from a "was undeclarable here" literal, two add one @ts-expect-error), and no console source changed. The conditionScope.ts and ConditionWidget.conditionScope.test.tsx prose edits are the same consequence: the restated name list would otherwise have been false.
  • Q2 — the reader census as the narrowing statement (seat answered A): accepted, re-derived above from the head's importer list and the non-test census. External hosts are covered by the changeset's one-line migration; they are not measurable from this tree.
  • Spec skew: observed, no card. At the tag (objectstack commit e5136ff10f1a0196d3ff4d971338a59506652297) FormField.visibleOn and FormSection.visibleOn are deprecated aliases that normalizeVisibleWhen folds at parse; on objectstack main (fd0e72abc2af5bd4d7056e7c2e387dc992d0f1df) both are retiredKey(FORM_FIELD_VISIBLE_ON_RETIRED) / retiredKey(FORM_SECTION_VISIBLE_ON_RETIRED). The designer's behaviour and the Omit entry are right under both; the skew closes with the next spec bump.
  • Stale prose left outside the surface: two of the three acceptance-note items are NOT stale for this key — flagged so no sweep follows them. The apps/console/README.md row "visible / visibleOn — Expression-based visibility conditions" sits under "Navigation Support" and names the navigation item key CommandPalette still reads; the packages/plugin-form/README.md row documents the runtime FormField.visibleOn slot that 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 deliberately kept (only its parenthetical "spec FormField.visibleOn" is dated). The RecordFormPage.tsx comment ("per-field visibleOn predicates") is the one genuinely stale mention. Prose only, no card owed; whoever touches those files repairs the one that is stale and leaves the two that are live.
  • ReportDefaultInspector.tsx was held outside the claim's surface pending fix(app-shell): a Name typed in a designer's create mode survives a later Label edit (objectui#12680) #12745; fix(app-shell): a Name typed in a designer's create mode survives a later Label edit (objectui#12680) #12745 merged at 2026-10-16T18:03Z before this branch's merge-forward, and the hunk is one doc-comment word. Accepted.
  • Serial constraints re-read: the only other open PR naming visibleWhen (fix(plugin-detail): the derived highlight strip skips members of a field group gated by visibleWhen (objectui#12720) #12769, plugin-detail) touches none of this PR's 26 paths.
  • Not this PR's, noted for the record: the CLI check's condition key list and CommandPalette's navigation visibleOn are different surfaces with their own spec keys; no finding here.

Implemented-by: claude/issue-12696-designer-visiblewhen
Reviewed-by: session_011K9g23W67dy2zwnyzaoB6o

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:ui seat 1 · session_011K9g23W67dy2zwnyzaoB6o · 2026-10-16T19:33Z

What was reviewed:

  • Head 875c761, against objectui#12696 (p2, target:v18).
  • The claim 6221456246 (Clause-②: no (narrowing)), and the dev's report with the seat's answers on the card.
  • The contract review of record, 6223192064 (CONTRACT_REVIEW_TIER, an isolated subagent, PASS, same head). It re-derived the reader census and the producer census, read the spec at the installed 18.0.0-next.2 tag and on objectstack main, and judged all seven derived judgments right.
  1. Shape.
    • Fixes #12696 is the first line and the only closing keyword. Clause-②: no (narrowing) starts line 2 of the body and a line in the committed changeset. The assignee is os-tesla.
    • 26 files, NOT governed. The source that moves is in packages/app-shell only. The four apps/console hunks are tests the type narrowing forced (accepted on the card, Q1).
  2. The narrowing.
    • SchemaForm reads visibleWhen only, at all five call sites.
    • FormFieldSpec and FormSectionSpec (both on the @object-ui/app-shell entry) drop visibleOn. 'visibleOn' stays in FormSectionSpec's Omit list, which is load-bearing, because the spec's input FormSection type still carries the key.
    • anchors.ts writes visibleWhen, and CONDITION_FIELD_NAMES drops visibleOn.
    • No in-repo producer and none of the 17 bundled spec authoring forms carries visibleOn (review ①2).
  3. Semver. @object-ui/app-shell minor with BREAKING stated and the one-line migration (write visibleWhen). This matches the repo's no-major policy in pre mode (Changeset Bump Policy: success).
  4. Gates (check-runs pinned to this head).
    • 33 success and 3 skipped (coverage and dependabot, skipped by design), including Lint, Type Check, Test with all 16 shards, Test (dist pins), Build & E2E, Bundle Analysis, Repo Gates, Governed Surface Queue Guard and every changeset check.
  5. Recorded, from the review.
    • Of the three "stale prose" items in the acceptance notes, two are live, not stale. The apps/console/README.md row names the navigation item's own visibleOn, which CommandPalette reads. The packages/plugin-form/README.md row documents the runtime FormField.visibleOn slot objectui#12654 kept. Only the RecordFormPage.tsx comment is stale. No card.
    • The changeset's "spec 18 retires" wording describes objectstack main; at the installed 18.0.0-next.2 the key is a folded deprecated alias. The host instruction is right either way.
  6. Landing.
    • Ready, then auto-merge into the merge queue.
    • After the merge: verify by content, close the card through Fixes, strip pm:dispatched, and archive cloud session session_011Nr8s6FN4wcgqMHMM7oZk4.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 19:35
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 19:35
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 236624d Oct 16, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12696-designer-visiblewhen branch October 16, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants