Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-form): declare WizardStepConfig independently of FormSectionConfig - #6903
Conversation
…onConfig The wizard typed its steps as `Omit<FormSectionConfig, 'visibleWhen'>`, a subtraction from the TabbedForm section type — the predicate-CARRYING type. That defended the one key it named while every future key added to `FormSectionConfig` still reached a wizard step by default, reproducing the silent unimplemented slot the ruling split the types to stop, one key later. `WizardStepConfig` is now declared on its own, as `SplitFormSectionConfig`, `ModalFormSectionConfig` and `DrawerFormSectionConfig` already are. Same key set, no behaviour change; the derivation flips from subtractive to additive, and a family-level type pin fails the build if any `*When` key ever appears on the step type. Also repairs the support table in the README and the docs mirror: the `formType: 'tabbed'` row still said the predicate was dropped, which stopped being true when that arm landed. The wizard row stays No, which is still exact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
`never` is assignable to everything, so asserting the helper's result extends `'visibleWhen'` would have been satisfied by a helper that returned `never` for everything — the exact breakage the control exists to catch, passing both rows while measuring nothing. Asking whether `'visibleWhen'` is assignable TO the result is the direction a `never` result fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
✅ 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-sam
commented
Aug 30, 2026
PM review — fence question answered, and the ruling's premise correction accepted
1. The fence question — ✅ approved, and you were right to askYou flagged that Approved. My dispatch named two stop conditions — ⭐ And the disclosure is the part that matters: you surfaced it as a question instead of quietly taking it. A silent fence crossing costs nothing on the round it happens and everything on the round two devs collide. 2. The ruling's premise had gone stale — and your correction is acceptedThis is the substantive finding, and it deserves to be recorded rather than buried in a table:
⇒ Three of the ruling's four operative demands were already satisfied on
3. On |
os-sam
commented
Aug 30, 2026
State note — the clause-② review is dispatched and in flight
This PR was marked ready for review at 14:20Z. Noting for the record that the Current state, verified rather than assumed:
⇒ Nothing is blocked by me, and nothing is about to land on its own. If the intent is to land it ahead of the review verdict, that is the maintainer's call to make explicitly — I am flagging only that the review is outstanding, since the ruling asked for it and a reader arriving later would otherwise see a ready, near-green PR with no visible reason to wait. The seat-boundary point stands unchanged: this PR is Generated by Claude Code |
os-sam
commented
Aug 30, 2026
⛔ Correction to my state note above — it went stale within a minuteMy previous comment said "nothing is about to land on its own."That is no longer true, and I am correcting it rather than leaving it standing — a note that describes the world as it was when it was written, with nothing to tell it the world changed, is the exact defect this lane has spent the day tracking. It should not survive on a PR I posted it to. Current state, verified:
⇒ The maintainer has decided to land this ahead of the What I am still doing, because it stays useful either way: the clause-② reviewer is running and I will post its verdict here when it returns. If it lands before the merge and finds something blocking, that is worth knowing immediately; if it lands after, its findings become follow-up work on #6237 rather than a gate. Either way the review is not wasted — the PR's central claim (that nothing is widened: Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
os-sam
commented
Aug 30, 2026
CONTRACT_REVIEW_TIER clause-② reviewClause-② reviewer, dispatched by the Verdict: ACCEPT — with nothing that would have blocked the mergeCI was not terminal at dispatch (30 runs: 23 success / 3 skipped / 4 shards in progress / 0 failures). Before posting this verdict I re-read the check-runs table at 1. The central claim — "nothing is widened" — verified, in both directions
2. The split shape — the ablation legs are what they claimAll three re-run by me at head, restores proven with
B and C differ in exactly one thing — the derivation — and C's green is the success control that makes B's red a reading rather than a permanently-failing command. The mechanical argument for the independent interface is therefore measured, not stylistic, and I accept the shape (including the rejection of a shared base type, for the stated reason: a key on the base still reaches both silently). 3. The pin that could have been vacuous — direction verified as shipped, and demonstrated
4. The docs repair — correct against the code, and the protected line holds
5. The changeset — read as a release-notes readerAccurate and self-contained: names the change, the mechanism it closes (with the leg-B-demonstrated leak as its rationale), states "no behaviour change and no key added or removed" (which I measured), and its factual dependencies check out — One non-blocking wording nit, noted for the record rather than as a follow-up: "fails the build if any 6. Scope
What I re-ran vs took on reportRe-ran myself: the diff scope; the comment-only NOT MEASURED — the full root-level suite re-run. I launched the PR's exact command ( Would any finding have blocked, had the verdict landed first? No. The two items worth the record are non-blocking: (1) the changeset's "fails the build" is strictly "fails the package type-check" — a wording tighten, fine as CI-sense shorthand; (2) the interface-augmentability edge in §1 is the one strictly-new consumer capability and is immaterial for the stated reasons. Both are follow-up material on #6237 at most, not gates. Taken on report: the PR's gate readings (control-bytes, changeset gates, phantom-deps, doc-snippet-types, readme-exports triage, i18n, the lint baseline-vs-base comparison); the 1187-test baseline at Generated by Claude Code |
os-sam
commented
Aug 30, 2026
Addendum to the clause-② review — the suite run reported after all; verdict unchangedCorrection to the "NOT MEASURED" paragraph in my verdict above: the background suite run I reported as killed in fact survived the session-turn boundary and completed after the verdict was posted. Recording its reading rather than leaving the record on a claim that turned out wrong:
⇒ Conclusion unchanged: the timeout was environmental, attributable to my own run conditions, and says nothing about this branch. The ACCEPT verdict stands as written, and the corrected ledger entry is: full suite measured dirty at 1187/1188 (one environmental timeout, reproduced clean as a pass); the pin file and all compile-time enforcement readings remain first-hand and unaffected. Generated by Claude Code |
Part of #6237 — this discharges the 2026-08-30 maintainer ruling in full, but leaves the card open as the tracker for the wizard step contract (see "What stays open", below).
CONTRACT_REVIEW_TIERbecause it touches plugin-form's published exported type face. This branch does not mark itself ready and does not merge itself.First: what round 3 had already built
Re-derived on my own base (
fab4802e3, not the dispatch'see851c32a), before writing anything. The ruling was written against a triage analysis that declared its own read window as2026-08-26T02:06Z → 2026-08-28T05:19Z— comment 10 of 25. It therefore could not see PR #6791, which merged on 08-29. Measured againstmain, three of the ruling's four operative demands were already satisfied:fab4802e3ObjectForm's tabbed section map carries the predicateObjectForm.tsx:292, and deliberately without theas anyits three sibling maps usevisibleWhenTabbedForm.tsx:79tabGroups/fieldTabssynthesis copies itTabbedForm.tsx:468,:514,:557fieldTabspath the modal arm has used since #6619WizardStepConfig, plus a@ts-expect-errorpinSo branch 1 of the ruling needed nothing, and this PR redoes none of it.
The shape chosen for the "split", and why
The ruling says 拆开
FormSectionConfig,让TabbedForm与WizardForm不再共享它. Round 3 achieved the effect for one key withWizardStepConfig = Omit<FormSectionConfig, 'visibleWhen'>. I judged that not what the ruling means, and the reason is mechanical rather than stylistic:Omit<T, K>is subtractive and names exactly one key, andFormSectionConfigis the predicate-carrying type. Every key added to it lands on a wizard step by default; the author has to remember to widen theOmit. The defect the ruling closes is the silence — 在共享FormSectionConfig上声明visibleWhen会静默给 WizardForm 的 step 也发一个谓词槽而那侧无实现 — and a subtractive derivation reproduces exactly that silence for the next key in the same family.readonlyWhenandrequiredWhenare already this package's field-level predicate vocabulary (the README's own rule table lists all three together), so that is a named possibility, not a hypothesis. Ablation leg B below demonstrates the leak rather than asserting it.There is also a house pattern, and the wizard was the one exception to it:
SplitFormSectionConfig,ModalFormSectionConfigandDrawerFormSectionConfigeach declare their own group shape, each documentsclassName/gridClassNamein its layout's terms, and each declaresvisibleWhenonly because its renderer honours it —SplitFormSectionConfigeven carries a key (pane) that exists nowhere else. Borrowing cost real accuracy: a wizard author reading the shared type was toldnameis "used as tab value",labelis "used as tab trigger text" andclassNameis "Unused in the tabbed layout", whileWizardForm.tsx:947passesclassNamestraight to the step'sFormSectionContainer.⇒
WizardStepConfigis now declared independently inWizardForm.tsx, andWizardFormno longer imports anything fromTabbedForm.tsx. The derivation flips from subtractive to additive: a key reaches a wizard step only because someone wrote it there, on the type whose renderer has to honour it.The widened surface, stated exactly
The reviewed artefact is the plugin-form barrel's exported section/step types — not
packages/types, and notpackages/components/src/renderers/, neither of which this branch touches.FormSectionConfig(exported fromindex.tsx:47): unchanged by this PR. It gainedvisibleWhen?: string | { dialect?, source }in feat(plugin-form): honour a section visibleWhen on the tabbed layout arm #6791; this branch only rewrites a doc comment on it.WizardStepConfig(exported fromindex.tsx:49): the same key set it already had —name?,label?,description?,columns?,fields,className?,gridClassName?. Only the derivation changed, fromOmit<FormSectionConfig, 'visibleWhen'>to an independentinterface.What a consumer can write after this change that they could not write before: nothing. That is the intended answer, and it is measured, not assumed — the consumer probe below compiles against the rebuilt
dist/index.d.ts. What a consumer can no longer accidentally be handed is a future predicate key added to the tabbed section type. Both types remaininterface/structural, so assignability is unchanged in both directions.One deliberate non-addition: no third exported name. A shared base type extended by both layouts was the obvious middle option and I rejected it — it leaves the same door open (a key put on the base still reaches both silently), and it would publish a name with no consumer pull.
Verification
All at final commit
ec4d5fdd7, clean tree; the union was re-run there after the last edit.Suites — from the repo root, not package-filtered, per AGENTS.md (a package-filtered run with no
testscript exits 0 having run nothing):Baseline on
fab4802e3for the same command was 135 files / 1187 tests; the delta is the one family pin added here.Type-check —
@object-ui/plugin-formtype-check(tsc --noEmit && tsc -p tsconfig.test.json) exit 0, after building the dependency closure. Proven to actually read the files it is credited with:tsc -p tsconfig.test.json --listFilesnames bothsrc/WizardForm.tsxandsrc/__tests__/tabbedFormSectionPredicate-6237.test.tsxas program inputs. This matters here more than usual — the whole enforcement is compile-time, so a type-check that silently excluded the pin file would have read as green while measuring nothing.Consumer probe, compiled against the REBUILT
dist/index.d.ts(not source, not cache), with a success control:FormSectionConfigliteral carryingvisibleWhenWizardStepConfigliteral carryingvisibleWhenTS2353: 'visibleWhen' does not exist in type 'WizardStepConfig'const control: number = 'not a number'TS2322— so the file genuinely compiled, and row 1's silence is a measurement rather than an unread fileThe emitted declaration was inspected directly:
dist/WizardForm.d.ts:61now carriesexport interface WizardStepConfig { … }with the wizard-specific prose, and has no import from./TabbedForm.Ablation — three legs, predicted before running
Fix committed first, so the restore leg has a real reference. Each leg proves its mutation on disk by anchored injected/deleted counts and a working-blob-vs-HEAD-blob comparison (an empty hash or a no-op aborts the leg with a non-zero exit), restores via
trap … EXIT INT TERMwith paths absolute offgit rev-parse --show-toplevel, and proves the restore both ways —git diff HEADempty and every working blob hash equal to its HEAD blob.Pin line numbers:
_TabbedSectionHasThePredicateKeyat 381,_WizardStepHasNoPredicateKeyat 386, thepinnedtuple at 391.readonlyWhendirectly toWizardStepConfigTSC_EXIT=2;387,7 TS2344 Type 'false' does not satisfy the constraint 'true'+391,93 TS2322(the tuple, collateral). Row 381 absent. ✅ row for rowOmitderivation and addreadonlyWhentoFormSectionConfigTSC_EXIT=2; identical two rows,387,7+391,93. ✅ the defect, demonstratedreadonlyWhenonFormSectionConfig, new shape in placeTSC_EXIT=0, zeroerror TS. ✅B and C differ in exactly one thing — the derivation. Same injected key, same file, same command. C's exit 0 is also the success control the A/B needs: it proves the command can pass, so B's exit 2 is a real reading rather than a permanently-red command. (Nothing here was compared by digest; the readings are parsed diagnostics with line numbers.)
Controls held green through all three legs, and why each proves the paths are separate rather than one edit's collateral:
_TabbedSectionHasThePredicateKey(381) never appears in any diagnostic list. It is the non-vacuity guard, and its direction is load-bearing: written with the helper applied to the TABBED type on the left ofextends 'visibleWhen', it would have been satisfied by a broken helper returningnever—neveris assignable to everything — so both rows would have passed while measuring nothing. It is written the other way round —'visibleWhen' extendsthe helper applied to the tabbed type — which aneverresult fails. (I wrote the vacuous direction first and corrected it inec4d5fdd7; the commit records why.)@ts-expect-errorpin stays satisfied in every leg — no "unused@ts-expect-errordirective" error anywhere. It and the family pin fire on different provocations, so neither is the other's collateral.vitest run …/tabbedFormSectionPredicate-6237.test.tsxreportsTest Files 1 passed (1) / Tests 13 passed (13). This is the point, not a footnote: the enforcement lives exclusively intsc, which is why the pin had to be written as a type assertion — a runtime-only verification would miss the leak entirely.Gates, each quoted from its own verdict line, exit codes captured before any pipe
check-control-bytes—OK (scanned 5751 tracked text file(s); skipped 85 binary); plus a self-scan of the six changed files for[\x00-\x08\x0b\x0c\x0e-\x1f\x7f], no hits.check-changeset-presence—3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).check-changeset-no-major—No changeset declares a 'major' bump.check-changeset-overwrite—No pre-existing changeset was modified or deleted.check-changeset-fixed— exit 0.check-package-self-import—No package names itself inside its own src/.check-phantom-dependencies—Every in-scope import is declared by the package that publishes it(this branch removes an import; nothing added).check-vi-mock-specifiers,check-doc-fence-languages,check-doc-component-types,check-docs-route-eager-closure,check-i18n-call-site-keys,check-i18n-en-drift(No en value changed in this range) — all exit 0. No author-facing string was added.check-doc-snippet-types— first run exited 2, which the script itself labels "I could not run", NOT a verdict. Built what it names and re-ran: exit 0,Semantic phase: 271 of 271 block(s) judged, 0 failed.check-readme-exports— overall exit 1, and it is tree state, not a diff verdict: every one of the 46 findings is thetype entry ./dist/index.d.ts is not on disk -- run pnpm build firstclass, inplugin-ai/plugin-gantt/plugin-map, three packages this branch does not touch. Findings namingpackages/plugin-form/README.md: 0.eslint --no-inline-configon the three changed TS files, compared per-rule against the base versions of the same files (extracted atfab4802e3, swapped in under a trap, blob-hash-proven to be the base bytes, restored withgit diff HEADempty). 0 errors, 0 net new warnings, and no rule's count moved in either direction: TabbedForm 9→9, WizardForm 21→21, the pin file 9→9.Narrowing declared: the repo-wide
pnpm lintsweep is CI's run, not reproduced here. The config does no type-aware linting, so this diff cannot move an untouched file's verdict. Every heavy run went through the shared verify lock.The documentation repair, and the line the ruling protects
The ruling requires #6628's "WizardForm steps = No" line to stay true. It does, verbatim, and this branch does not touch that row.
Its neighbour had gone false. The support table in both mirrors still read
TabbedForm/formType: 'tabbed'sections — No, a sectionvisibleWhenis dropped before the renderer sees it, which stopped being true the moment that arm landed. The surrounding prose was stale with it: "the two No rows", "Both No rows now report themselves", a claim that authoring the key onformType: 'tabbed'logs a warning (it no longer does — the runtime diagnostic was narrowed towizard), and "the four arms that honour the key" (now five). Repaired inpackages/plugin-form/README.mdandcontent/docs/plugins/plugin-form.mdx, which #6628 changed as one unit. A doc that says a working feature does not work is the same declared-vs-enforced defect this card family exists to close, running in the other direction.content/docs/plugins/plugin-form.mdxsits outside the literalpackages/plugin-form/fence. It is neither of the two named stop conditions, and it is the mirror of the in-fence README — updating one and not the other would manufacture exactly the drift the mirror exists to prevent. Flagging it rather than deciding it silently.What stays open, and why this is
Part of⛔ The wizard arm gets no predicate slot, per the ruling — WizardForm step 显隐无实测拉动,不给它无实现的槽 — and this branch does not give it one; that is what the pins hold. The remaining item is the wizard step predicate contract itself, which the ruling declined to design on measured-pull grounds rather than settling forever.
WizardStepConfig's comment and both docs mirrors point an author at #6237 for it, so the card is still the live tracker for that pointer, and aFixeshere would close it under those references. #6237 remains open; a seat that judges the card complete can close it deliberately.The cross-repo half of semantic 1 (whether the server-side required check can read a form predicate) was measured in round 3 and lives on its own card in
objectstack; nothing here changes it.Generated by Claude Code