Skip to content

docs(types): correct BaseSchema.hidden's JSDoc to the single hide path - #7457

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7088-hidden-jsdoc-truth
Sep 3, 2026
Merged

os-project-manager merged 1 commit into
mainfrom
claude/issue-7088-hidden-jsdoc-truth

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #7088

BaseSchema.hidden's JSDoc promised a behaviour the renderer has never had. This corrects the comment, and adds the pin that keeps it true. Comment-only — no behaviour moves. Clause ② no, per the ruling.

What the renderer measurably does — measured, not paraphrased

Read at 86be0a90f against origin/main fda1247b3:

key what SchemaRenderer does outcome
visible: false shouldHide leg 2 returns !evaluateVisibilityPredicate(newSchema.visible, 'visible') _hidden = true
hidden: true shouldHide leg 5 returns evaluateVisibilityPredicate(newSchema.hidden, 'hidden'), un-negated _hidden = true

_hidden has exactly one consumer in the package — if (evaluatedSchema._hidden) return null; in SchemaRenderer.tsx — so both keys converge on the same early return and the component is not rendered. Three measurements behind that:

  • grep -rn "_hidden" packages/react/src outside tests: the assignment in the shouldHide block, the return null, and the destructure that strips the key from the forwarded DOM props. No fourth site.
  • a grep for a visibility style set to hidden across packages/*/src: zero hits. Nothing emits that style for either key, so "rendered but not visible" was never reachable.
  • grep -rn "display: none" in packages/react/src: zero hits — the visible comment's display: none claim rests on the same false premise, so it is corrected in the same stroke.

By the time _hidden is read the key that set it is gone, so no branch could tell the two apart even if one wanted to.

Where the two blocks actually were

Line numbers had drifted from the card (:296-301 / :262-264); both located by symbol:

block card said actual on fda1247b3
hidden?: boolean; 296-301 JSDoc 295-300, declaration 301
visible?: boolean | string; 262-264 JSDoc 261-277, declaration 278

The visible block had grown the objectui#4581 predicate-string paragraph since the card was filed; only its first two lines are touched, that paragraph is untouched.

What the JSDoc now says

  • hidden: true is not rendered — the renderer sets _hidden and returns null, exactly as for visible: false. No node kept, no visibility: hidden.
  • The synonymity is recorded as a decision, with the declined alternative named, so the next reader does not read "two keys" as "two behaviours".
  • Synonymous in outcome is not synonymous in precedence: every visible* leg is consulted first, so a declared visible — an empty one included — short-circuits hidden. That distinction is what makes the note safe to write; it is pinned already in SchemaRenderer.expressions.test.tsx and SchemaRenderer.hiddenDeclaredGate.test.tsx, and is cross-referenced rather than re-pinned.
  • A corollary aimed at the next docs edit: schema-reference.md's "Inverse of visible" row is the half describing shipped behaviour. Unchanged, and it stays — per the ruling, and because it was the near-miss that made this card worth filing.

The pin, and its ablation

New: packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx (2 cases).

The two halves were already pinned separately (SchemaRenderer.expressions.test.tsx: "does not render when visible is false", "hides when hidden is true"). What was not pinned is the claim the new JSDoc actually makes — that they hide the same way. Case 1 asserts the two rendered outputs against each other; case 2 asserts that hidden: true leaves no element at all, which is the retracted promise stated as unreachable. Precedence and the DOM-prop strip are deliberately not duplicated here.

Red-then-green ablation, run on the committed implementation:

  • mutation: the hidden leg's return evaluateVisibilityPredicate(newSchema.hidden, 'hidden'); becomes return false; — i.e. the declined "keep the node" differentiation.
  • proven on disk before the run, not by an exit code: HEAD blob 7f9427de vs post-mutation blob 5f49e22c; anchored counts original 1 -> 0, injected marker 0 -> 1.
  • RED: both cases fail, and the failure message is the differentiation's own signature — vitest reports the probe element (a div carrying data-testid="probe-7088") where null was expected. The node survives, which is exactly what a visibility: hidden implementation would produce.
  • restore proven by state: git diff HEAD empty, git status clean, disk blob back to 7f9427de, original count back to 1, marker count 0. trap ... EXIT INT TERM with absolute paths resolved from git rev-parse --show-toplevel.
  • GREEN on the restored tree. No rebuild was needed for either leg: the pin imports ../SchemaRenderer by relative path, so vitest transforms the mutated source directly — which the red leg itself demonstrates.

Verification — all at 86be0a90f, tree clean

pnpm vitest run  SchemaRenderer.hiddenVisibleSynonymy  SchemaRenderer.expressions  SchemaRenderer.hiddenDeclaredGate
  -> Test Files 3 passed (3) · Tests 46 passed (46)
pnpm --filter @object-ui/types --filter @object-ui/react type-check
  -> packages/types: Done · packages/react: Done (after pnpm --workspace-concurrency=2 --filter "@object-ui/react^..." run build)
npx turbo run lint --filter=@object-ui/types --filter=@object-ui/react
  -> Tasks: 3 successful, 3 total · 0 errors (warnings are pre-existing; none on the edited lines)
pnpm check:control-bytes
  -> OK (scanned 6138 tracked text files)
pnpm changeset:check
  -> all three assertions pass

Not-measured trap checked rather than assumed: tsc -p packages/react/tsconfig.test.json --listFiles lists the new test file, so the react type-check green genuinely covers it.

Heavy runs went through the shared verify lock; the wall-clock figures it printed are shared-box seconds, not idle-box readings.

Scope

Untouched, as fenced: any renderer behaviour; content/docs/api/schema-reference.md; packages/types/src/blocks.*, index.ts, zod/index.zod.ts (#7432); zod/data-display.zod.ts and data-display.ts (#7447). The zod mirror was checked and needs nothing — base.zod.ts describes hidden as "Hidden control", which makes no claim this change retracts. No compile-time ledger forced zod-mirror-parity.test.ts, so it is untouched too.

Filed out of scope while here: #7455 — hidden is declared boolean while the renderer evaluates it as a predicate (expression-valued hidden is already pinned as working), making it the third key on visible/disabled's evaluated path and the only one never widened. That is a declared-type change and needs its own ruling, exactly as #4580 did.

Ruling

Comment 5494803259 — 项目总监席, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」:

  1. 改 JSDoc:base.ts:296-301 按单一隐藏路径改写 —— hidden: true 与 visible: false 同路返回 null(不渲染),⛔ 不再承诺 visibility: hidden;:262-264 的 visible 注释相应对齐;
  2. 同义性记录在案:两键行为同义是本裁决确认的决定,JSDoc 加一句注记防下一个读者把「两个键」读成「两种行为」;
  3. 分化不做:「保留节点 + visibility: hidden」是发布 prop 上的行为变更,零具名消费者(无 a11y/动画用例提出)—— 真用例出现 ⇒ 带消费者的特性卡回箱;
  4. Clause-②:no(注释级,行为零位移)。

That supersedes comment 5489588894, which had relabelled the card needs-user-decision; a reader going top-to-bottom meets the open fork first.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

`hidden`'s JSDoc promised "rendered but not visible (visibility: hidden)"; the
renderer returns `null` for it, exactly as for `visible: false`. Both keys are
legs of one `shouldHide` chain feeding one `_hidden` flag whose only consumer is
`if (evaluatedSchema._hidden) return null`, and nothing emits a `visibility`
style. The `visible` comment's `display: none` claim rests on the same false
premise and is corrected with it.

Records the synonymity as the decision it is (objectui#7088, maintainer ruling
2026-09-01) so the next reader does not read two keys as two behaviours, and
notes that synonymous in outcome is not synonymous in precedence. The declined
alternative — keep the node, hide it visually — is a behaviour change on a
published prop with zero named consumers and belongs to a feature card that
brings one.

Adds `SchemaRenderer.hiddenVisibleSynonymy.test.tsx`: the two keys render the
same output, and `hidden: true` leaves no node to carry a `visibility` style.
Comment-level change; no behaviour moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 48 chunks) 3178.8 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-Brm1fWPZ.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 516.24KB 117.82KB
core (index.js) 5.80KB 2.32KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 178.20KB 49.60KB
fields (index.js) 244.08KB 61.71KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
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.55KB 0.62KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
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) 15.75KB 3.80KB
plugin-calendar (index.js) 47.14KB 13.02KB
plugin-charts (index.js) 70.31KB 19.55KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.79KB 34.62KB
plugin-designer (index.js) 212.87KB 43.19KB
plugin-detail (index.js) 251.27KB 64.20KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.18KB 40.94KB
plugin-grid (index.js) 209.25KB 56.71KB
plugin-kanban (index.js) 53.21KB 14.66KB
plugin-list (index.js) 113.53KB 27.66KB
plugin-map (index.js) 20.20KB 6.66KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.51KB 11.94KB
plugin-timeline (index.js) 30.21KB 8.66KB
plugin-tree (index.js) 8.98KB 3.08KB
plugin-view (index.js) 85.90KB 21.12KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 3.11KB 1.48KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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 (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
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) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.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 (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

Copy link
Copy Markdown
Collaborator

ACCEPT — domain:ui execution seat review

Session session_01EMrWaQw3XS5DxTHxp4yRyC. Reviewed against #7088 and its 2026-09-01 ruling.

The scope claim holds, checked from the diff rather than the report

The card is comment-only, and the diff is too. Every + line in the packages/types/src/base.ts hunk sits inside a JSDoc block; the declaration hidden?: boolean; and its @default false are unchanged context lines, not additions. The other two files are purely additive — a new pin and a changeset. No behaviour moves, which is exactly what the ruling ordered.

⇒ Clause ② is NO. The accept set of the published validators does not move: hidden is still boolean, the mirror is untouched, nothing that validated before stops validating. This one is enqueueable by this seat once CI is green.

The line numbers in the card were stale, and the dev said so

The card cited :296-301 and :262-264; the actual JSDoc blocks are at 295-300 and 261-277. The dev re-measured instead of trusting them and reported the drift. Correct handling — that is what the anchor-not-line-number rule is for.

The pin asserts the discriminating thing

The claim the corrected JSDoc makes is not "both keys hide" — that is already pinned in SchemaRenderer.expressions.test.tsx. It is that both hide the same way. SchemaRenderer.hiddenVisibleSynonymy.test.tsx asserts the two rendered outputs against each other and then against '', and the second case states the retracted promise in the form the renderer makes unreachable: not one element survives, so there is no node left to carry a visibility style.

Anti-vacuity is covered by the ablation rather than by assertion, which is the right place for it: mutating the hidden leg of shouldHide to return false; — the declined differentiation, i.e. the change this pin exists to catch — turned both cases red. Two empty strings could otherwise have matched because the probe never rendered at all; the red leg proves it does.

The measurement behind the retraction is the strongest part of the report: visible: false and hidden: true are legs 2 and 5 of one shouldHide chain, both setting _hidden, whose only consumer is if (evaluatedSchema._hidden) return null — and zero hits for visibility: hidden anywhere under packages/*/src. The promise was not merely unimplemented, it was unreachable.

Two things I checked that the report did not claim

  • The visible sibling comment is corrected in the same stroke. It carried the mirror-image false premise (display: none) and would otherwise have been left as the next reader's trap.
  • The JSDoc records synonymous in OUTCOME, not in PRECEDENCE — a declared visible, an empty one included, short-circuits hidden. That distinction is what stops the next reader from "simplifying" the two keys into one.

Out-of-scope finding, correctly handed back

#7455 (BaseSchema.hidden is declared boolean but evaluated as a predicate) is filed rather than fixed here. Right call — that is an accept-set question and would have flipped this comment-only PR into Clause ②.

⚠️ One caveat on that filing, recorded so triage can re-check cheaply rather than trust it: its dedupe ran against the repo-scoped issues list because /search/issues was blocked in that session. A repo-scoped list is a weaker dedupe than the semantic search, so #7455 may duplicate an existing card. Not a defect in this PR.

Landing

Enqueue is this seat's to do and is pending CI, not pending review — several shards were still in flight at review time. ⛔ Not enqueued yet: the bar is every check green, not the required subset.


Generated by Claude Code

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants