Uh oh!
There was an error while loading. Please reload this page.
Honour per-option visibleWhen in the metadata-admin renderer - #6618
Conversation
SelectOptionSchema declares a per-option `visibleWhen` (ADR-0068) and is z.core.$strict, so a *.form.ts carrying one parses clean — but all three metadata-admin controls that consume `fieldSpec.options` mapped the authored list straight to items and never read the key. Accepted, stored, shipped, ignored: ADR-0049's declared-but-unenforced shape, failing in the permissive direction (the option stayed offered). Per the maintainer ruling A2 + B1 + C1: - A2: bind the four ADR-0068 D1 identity spellings alongside `data`, selected out of the host ExpressionProvider's bag rather than copied. `data` stays the draft — the provider's conflicting `data` key is NOT adopted. `record`/`app`/ `features` stay unbound so they keep raising the loud diagnostic, which now reads the bound names off the actual scope instead of claiming "the only name is `data`". No third evaluator: the filter routes through evaluatePredicate. - B1: face decisions keep reading the RAW option list; only the rendered list is filtered, so an emptied set is an empty picker — never the free-text degradation, never a different widget registration. - C1: no pruning; MultiSelectWidget's toggle re-orders against the raw list so a hidden-but-selected value survives. FormFieldSpec.options now derives from the spec's SelectOption with its narrowings named in an Omit, instead of hand-mirroring three of five keys. A pin per consuming control, each asserting an option ABSENT on a false predicate — the positive case cannot distinguish fail-open from a fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
`tsconfig.test.json` compiles this suite, and it caught the fixture typing its options as `Record<string, unknown>[]` and its `type` as a `'default'` that is not in FormView's enum. A loose fixture would have let the suite pass while proving nothing about the AUTHORING type this card widened — the annotation is itself the check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
✅ 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-sales
commented
Aug 27, 2026
ACCEPT on substance (ui execution seat, Why the verification convinces⭐⭐ The load-bearing insight is the fail-direction one: this evaluator fails OPEN, so "the option is shown" is equally the outcome of predicate-true, predicate-never-arrived and predicate-faulted. Every pin therefore asserts an option ABSENT on a false predicate, with shown-when-true kept only as controls. A positive-only suite would pass against the unfixed renderer — which is exactly how a legally-authored per-option The ablation was designed, not just run: reverting only ⭐ Two red-looking signatures correctly classified as not-red
Also right: three scripts first returning EXIT=254 were recognised as pnpm "no such script" rather than failures, and re-run under their real names. The B1 and C1 halves are where the value isB1 — face decisions keep reading the raw list, so an emptied set renders an empty picker. The measured consequences of getting this wrong are the reason it matters: the builtin Select would have fallen through to C1 — no pruning, ⭐ including the quiet path: Rider accepted: deriving Fence
Retraction, restated here so it cannot be re-derived a third timeI read the Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6247
Authored by Claude Code, session
session_01CRJge11jso9TpXRWFt1Z49(https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49) — recorded here in prose because a body edit rewrites the footer below to its bare form.SelectOptionSchemadeclares a per-optionvisibleWhen(ADR-0068 / #2284) and the schema isz.core.$strict— an undeclared sibling key is refused withunrecognized_keys— so the key is a real declaration and a*.form.tscarrying a per-option predicate parses clean. The metadata-admin renderer never read it: all three controls that consumefieldSpec.optionsmapped the authored list straight to items. Accepted, stored, shipped, ignored — ADR-0049's declared-but-unenforced shape, failing in the permissive direction (the option stayed offered).This implements the maintainer ruling A2 + B1 + C1, affirmed three times on the card (2026-08-25T06:24Z batch 4; 2026-08-25T10:32Z upholding A2 against the A1 counter-proposal; 2026-08-27 decision-inbox batch 2 declining the A0 alternative). No fork is re-litigated here.
What changed
FORK A → A2 + A1's diagnostic.
SchemaForm'sevaluatePredicatectx now binds the four ADR-0068 D1 identity spellings —current_user,user,ctx.user,os.user— alongsidedata, selected out of the hostExpressionProvider's bag rather than copied, so the alias set cannot drift frombuildExpressionScope.datastays the draft: the provider's bag also carries adatakey meaning its own data scope, and adopting that is the #5926 gap-2 collision the ruling's own text excludes.record,appandfeaturesstay unbound so they keep raising the loud warn-once diagnostic instead of resolving to a silentundefined—predicate.test.tsalready pinsrecord.statusas a warning, and that pin still passes. No third evaluator: the option filter routes through the sameevaluatePredicatethe section, field and repeater-row gates use. All five existing call sites in the file now build their ctx through the onebuildPredicateCtx, socurrent_usercannot mean one thing in an option gate and another in the field gate beside it.The diagnostic itself had to change with the binding: it asserted "the only name is
data", which stopped being true the moment identity was bound. It now reads the bound names off the actual scope. A diagnostic that lies is worse than none — it sends the author to un-write a spelling that is in fact correct.FORK B → B1. Face decisions keep reading the raw option list —
resolveFieldFace'shasOptions,resolveColorWidgetKey, and eachoptions.length > 0branch condition — and only the rendered list is filtered. An emptied set renders an empty picker. Measured consequences of getting this wrong, all three now pinned: the builtin Select would have fallen through tostring → Input(a free-text box for "withdraw every option"),MultiSelectWidgetwould have degraded to its comma-tag editor, and a fully-withdrawn palette would have flippedresolveColorWidgetKeyfromcolor-pickertocolor-input— making the labelling channel #4871 point 4 deliberately fixes in the host predicate-dependent.FORK C → C1. No pruning. This includes the quiet path:
MultiSelectWidget'stogglere-orders the selection against the raw list, because ordering against the filtered one would have dropped a hidden-but-selected value on the next unrelated click — pruning authored metadata through the back door, with no author action that says "remove this".Rider —
FormFieldSpec.optionsis no longer hand-written. It derives from the spec'sSelectOptionwith its narrowings named in anOmit, per this file's own convention:visibleWhenre-pointed to the localVisibilityPredicate, anddefaultdropped and now saying so (nothing on this surface reads it; #6263 owns that key). Two of the spec's five option keys had been dropped by silence — which is exactly how a legally-authored per-optionvisibleWhencame to parse clean and render inert.Scope note — one file beyond the dispatched fence
The dispatch fenced
SchemaForm.tsx/widgets.tsx/form-spec.ts+ tests. This PR also editspredicate.ts(same directory). It is mechanically required by the ruling, which names "the loud warn-once dev diagnostic (predicate.tsmachinery)": the ctx type wasdata-only and had to widen to carry the identity roots, and the diagnostic's hardcoded "the only name isdata" had to stop being false. The alternative was a type lie plus a diagnostic that misleads — worse on contract-first grounds. Flagged rather than done silently. (Reviewed and accepted by the dispatching seat: the ruling names that machinery, so the dispatch fence was drawn too narrow.)Verification
Union re-run at the final commit
06b7d6a3, after the last commit landed — the whole@object-ui/app-shellpackage suite plusapps/console's type-parity suite: 563 files / 5433 passed, 1 skipped, exit 0.pnpm --filter @object-ui/app-shell type-checkexit 0 / 0 errors.lintexit 0 with 0 errors across 1003 linted files (counted from--format json); the single warning attributed topredicate.tsis a pre-existinganyannotation present onorigin/maintoo.SchemaForm.tsx+widgets.tsxtoorigin/main(keepingpredicate.ts, so the pins fail on behaviour rather than on a missing import) turns 8 of 15 red: every starred assertion, one per consuming control plus the fork pins. The 7 that stay green are the positive controls and the purebuildPredicateCtxblock — which is the designed split. Mutation proven on disk on both legs (injected text absent, removed text back); restore proven byte-identical to the HEAD blob bygit hash-object, plus an emptygit diff HEAD --stat.--filter '...@object-ui/app-shell', the prefix form = dependents): all 4 consumers green. This mattered —apps/console'sFormPage.viewSpec.test.tsasserts an exact type equality between its own form-view type and app-shell's, transitively covering the widenedoptions. Predicted this could break; measured that it does not, because the console derives that type rather than restating it. A first run showed 16 errors that were entirely unbuilt siblings (TS2307); building the console's closure took it to 0.tsconfig.test.jsonreally does compile the new suite — proven, not assumed: it rejected the first fixture (a formtypeoutside FormView's enum, andoptionstyped loosely enough to prove nothing about the widened authoring type). Fixed in the second commit.check:spec-symbols,check:phantom-deps,check:self-import,check:esm-specifiers,check:vi-mock-specifiers,check:designer-field-key-parity,changeset:check(nomajor),check-changeset-presence,check-changeset-overwrite,check:control-bytes,lint:coverage,type-check:coverage— all exit 0.Deliberately not fixed here — and it bounds the claim
This interim evaluator's
inrequires an array literal on the right, so ADR-0068's own headline spelling'admin' in current_user.positions— membership against a path — falls through to the bare-truthy branch and evaluates TRUE regardless of the user, silently.inbranch at all and the root is never resolved.parseLiteralreturns the whole expression verbatim as a truthy string. Bindingcurrent_usertherefore cannot change the outcome in either direction, and thePATH_SHAPED_LITERALdiagnostic never fired for it either (that one only matches text starting with an identifier character). Measured both ways and root-independent:'x' in data.tagsis equally inert, while the literal-set controldata.kind in ['a','b']discriminates correctly. So this PR neither creates nor worsens it — the behaviour is silently TRUE before and after.Filed as #6617, which owns closing it; that number is recorded here for reference and is not addressed by this PR. An implementer's feasibility note on its "diagnose only" direction is recorded on that card.
The spellings this change makes genuinely discriminate are the documented subset:
path == literal,path != literal,path in [literals],!path,path,&&,||.