Uh oh!
There was an error while loading. Please reload this page.
feat(types): declare the predicate string BaseSchema.hidden already evaluates - #7543
Conversation
… evaluates `hidden?: boolean` becomes `boolean | string`, and the Zod mirror's `z.boolean()` becomes `z.union([z.boolean(), z.string()])` — matching `visible` and `disabled` on both faces. `hidden` was the third key on the same evaluated path and the only one still declared boolean-only. The renderer never read this key as a boolean: `SchemaRenderer`'s `shouldHide` chain asks `hasDeclaredPredicate(newSchema.hidden)` and then evaluates the value. Measured before this change: a predicate string on `hidden` failed `safeParse` with `invalid_type` at path `hidden`, while the identical string on `visible` parsed. Behaviour is unchanged — this is a declaration catching up with a shipped, pinned capability. The docs row now states what the renderer does, the react pin drives the string form through the declared path with no cast, and a new types-side pin holds the three keys to one declared type. The CEL envelope object form stays undeclared on all three keys; objectui#7530 rules on all three together. Ruled by the maintainer on 2026-09-03, objectui#7455 option A. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
… exposes `AppComponentSchema` is `BaseSchema.extend(SpecAppFields.shape)`, and `@objectstack/spec@17.2.0`'s `AppSchema` declares `hidden` (boolean-only) while declaring neither `visible` nor `disabled`. So widening the base moved only the DECLARED face of that pair, and `zod-mirror-parity` named it. Seeded as a `KnownDrift` entry with the measurement and the reason, per that file's own instruction to fix a firing by measuring. The collision underneath — the spec's app-catalogue flag and the renderer's hide predicate sharing one name — is a contract question, filed separately and not decided here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
✅ Console Performance Budget
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
Size Limits
|
os-project-manager
commented
Sep 3, 2026
合约审查裁断:PASS —— 逐字采信
转录档位核验:只提取 harness 生成的 VERDICT: PASSReviewed: PR #7543, head What I measured, and what each ruled outClaim 1 — the widening is exactly Claim 2 — the envelope form is declared nowhere. Claim 3 — the docs row is hand-written. Claim 4 — the Claim 5 — red-first on the base. Base mirror: Claim 6 — reverse verification. Mutation (both faces back to boolean, anchors 1/1 each way, dist rebuilt and re-read each way, restore proved by blob hashes equal to Claim 7 — lint population. Claim 8 — changeset. ADR-0089 / JSDoc honesty. The quoted sentence is verbatim from the ADR's TL;DR; its Decision section scopes Consumer sweep and merge.Forced full build ( Non-blocking observations (no action required for the verdict)
NARROWINGS
派发席处置**放行。** ⭐ 本次按新立的次序执行:先 undraft + auto-merge、成功后再剥 观察 2 已记为独立跟进项: Generated by Claude Code |
os-project-manager
commented
Sep 3, 2026
放行 — 合约复核 PASS,已 ready + auto-merge (SQUASH)标签 一处记录(⛔ 不据此扣住本 PR,不要求修改)本 PR 编辑了 但表头的
⛔ 这不是对本 PR 的指摘。
所以这不是「表头烂了、重写一遍就好」。#6141 已经用「重写一遍」补过一次——它加的那段小标题正是「每个数字是怎么来的」,开头一句是「 表头自己也把原因写明了:「Nothing asserts it against a written number, so this line is prose and can rot」。 ⇒ 我会把它与另一处同类( Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7455
Implements the maintainer ruling recorded 2026-09-03 on that card (option A: widen), decision batch #24.
Clause-②: yes — a published type surface widens. Draft +
needs:contract-review; ⛔ not marked ready, no auto-merge.Measurements below are at
f7b8948b1, the head of this branch, taken afterorigin/mainwas merged in.What changed
packages/types/src/base.tshidden?: booleanhidden?: boolean | stringpackages/types/src/zod/base.zod.tsz.boolean()z.union([z.boolean(), z.string()])content/docs/api/schema-reference.md:72visible, this key takes no expression."hiddenOnnamed as the sibling spellingSchemaRenderer.hiddenDeclaredGate.test.tsxRecordhelper and a castPlus the JSDoc block on
hidden(recording the reasoning the wayvisibleanddisabled's blocks already do, and noting that ADR-0089 governspackages/spec's keys and not this surface), a new types-side pin, oneKnownDriftentry explained below, and aminorchangeset on@object-ui/types.⛔ This is not "consistent with objectui#4581 / objectui#4580-Q3-A", and the PR does not say that
The precedent is incomplete, and the triage on the card measured why. The CEL envelope object form is accepted by the shared evaluator on all three keys and is declared on none of them —
visibleanddisabledareboolean \| stringand under-report it exactly ashiddendid. objectui#7530 carries that question for all three together.So, per key, after this PR:
visible— boolean and string declared on both faces; envelope undeclared.hidden— boolean and string declared on both faces as of this PR; envelope undeclared.disabled— boolean and string declared on both faces; envelope undeclared.⛔ Nothing here declares the envelope on
hidden. The pin that exercises it keeps the helper and the cast it already used, deliberately.Verification I re-derived rather than inherited
The three keys' shapes on this branch's base (
d04e79a80), both faces. The triage read them atac8d523; the line numbers moved, the readings reproduce:base.tsbase.zod.tsvisibleboolean | string:281hiddenboolean:328z.boolean():175disabledboolean | string:354The triage's claim that the Zod mirror is NOT already ahead of TS — re-measured, and it holds. Run against the unmodified base tree:
Both faces refused the string. The specific line the triage cited (
base.zod.ts:170) isvisibleOnon this base;hiddenOnsits at :180. Both arez.string(), so the citation had drifted by a line but the conclusion it supported is correct.The docs row is hand-written, not generated — so the stop-condition on that row does not fire. Four readings, with a lit control on the one that could otherwise return a confident zero:
git grep -liE 'DO NOT EDIT|@generated|automatically generated');content/docs/api/schema-reference.mdcarries none. The control proves the probe fires.git check-ignoredoes not ignore it — it is a tracked file.check-doc-component-types.mjs,check-doc-snippet-types.mjs) only reads it.ab7dc31ce"state every declared BaseSchema member at its declared type".Reverse verification — direction predicted before running
Mutation: restore
hiddento boolean-only on both faces, keep every new pin. Mutation and restore each proved on disk by anchor counts in both directions; the rebuiltdistwas re-read each way so nothing was measured against a stale.d.ts; the restore was proved by state (git diff HEADempty and both worktree blob hashes byte-identical to theirHEADblobs), never by an exit code.Predicted RED, observed RED, no surprises:
base-schema-hidden-predicate.test.tsinvariantEqualassertions (3)× zod mirror: a predicate string on 'hidden' parses in fullPredicted GREEN and observed GREEN in both states — the carriers are legal either way:
SchemaRenderer.hiddenDeclaredGate.test.tsx, including the new declared-path one. A declaration change cannot move runtime behaviour, and this is the control that says so.base-schema-visible-predicate.test.ts, and thevisible/disabledcontrol cases inside the new pin.hidden: true/hidden: falseparse, andhidden: 123is refused. The refusal is the anti-overshoot guard: widening toz.any()would satisfy every positive case on its own.One prediction I got wrong, and it was a NOT-MEASURED rather than a green. The first mutation run measured
@object-ui/typesand@object-ui/reactin one recursivepnpm --filterinvocation. It stopped at the first failing package (ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL), so the react leg did not run at all — reading its absence from the output as "react stayed green" would have been wrong in the most ordinary way. It was re-run on its own, under the same mutation, and produced the two predicted errors and nothing else.⭐ One thing the ruling could not have known: the widening seeds a ledger entry
zod-mirror-parity.test.tsturned red namingapp.zod.ts#AppComponentSchema— a pair with no ledger entry, so its recorded drift wasnever. Cause, measured on@objectstack/spec@17.2.0by resolvingAppSchema.shapeat runtime:AppSchemadeclareshidden(z.boolean().optional()— accepts a boolean, refuses a string) and declares neithervisiblenordisabled;AppComponentSchemaisBaseSchema.extend(SpecAppFields.shape).extend(...), andSpecAppFieldsexcludes six keys withhiddennot among them — so on the mirror face the spec's boolean lands after the base's and overrides it;extends BaseSchemaand does not restate the key, so it inherits the widened union.That asymmetry is the spec's, one layer under the one this card removed:
visibleanddisabledwidened without seeding anything precisely because the spec'sAppSchemadoes not declare them. Attribution is measured, not assumed — under the mutation above, this error disappears.Handled the way that file instructs a firing to be handled: by measuring, with a
KnownDriftentry carrying the measurement and the reason.ReconcileAgainstLedgercompares invariantly, so the file going green proves the entry is exactly the measured drift — the single keyhidden, no more and no less. The ledger's prose counts moved with it (39/55 to 40/56, and the two "pairs with no entry" figures).⛔ I did not close it by dropping
hiddenfromSpecAppFields: that would make a spec-derived schema accept, by local divergence, a value the spec refuses — a second published surface, outside this ruling. The collision underneath (the spec's app-catalogue flag and the renderer's hide predicate sharing one name) is filed as objectui#7542, unassigned, with the directions measured and none chosen.What was run, at
f7b8948b1pnpm build(whole repo)@object-ui/types, all of them, green.pnpm --filter @object-ui/types type-check(tsc+ examples +tsconfig.test.json)error TSlinespnpm --filter @object-ui/react type-check(tsc+tsconfig.test.json)error TSlinespnpm exec vitest run packages/types/pnpm exec vitest runon 6 react visibility/enablement suitescheck:control-bytescheck:doc-types/check:doc-fences/check:doc-snippetscheck:phantom-deps/check:published-tsconfig-exclude/check:readme-exportscheck-changeset-presence/check-changeset-no-majormajorcheck-governed-queue-guard --testover all 7 changed pathspnpm lint(eslint . --no-inline-config, full repo, not narrowed)The test file the cast was dropped in is genuinely compiled:
tsc -p packages/react/tsconfig.test.json --listFilesreports it, so the type-level half of that pin is checked rather than merely written.Not measured, declared: remote CI. Per the dispatch contract the report is handed over at draft-PR time and CI convergence is the reviewing seat's; nothing here waits on it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Generated by Claude Code