Skip to content

fix(types,components,plugin-form,console,core): a faulted or blank visibleWhen refuses the submit, naming the field and the rule; a blank field rule is refused at authoring; blank gates are diagnosed (objectui#8069) - #11233

Merged
objectstack-fleet[bot] merged 11 commits into
mainfrom
claude/issue-8069-visiblewhen-fault-refuses-submit
Sep 30, 2026

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #8069
Clause-②: yes (narrowing)

A field visibleWhen that cannot be evaluated now refuses the submit, naming the field and the rule, on the three submit paths the ruling names: the record form renderer (form.tsx, so every ObjectForm layout), the console's FormPage (/forms/:name and /f/:slug), and the wizard's cross-step gate at final submit. requiredWhen / readonlyWhen are unchanged on the client; the server's ADR-0137 D2 refusal of them lands beside the input. Both blank-gate silencers are diagnosed, with no verdict changed.

Ruling: card comment 5910115531 (batch #256 item 4) — Q1 = B, one judge per rule; Q2 = B as seat 1 answered it in 5908497249. Dispatch claim: 5912071659.

Narrowing

  • Accepted before: a form whose field visibleWhen faulted (typo in a column, syntax error, unbound root) or was stored BLANK drew the field (fail-open) and submitted as if the rule had said "show"; and objectui's FormFieldSchema accepted a blank visibleWhen / readonlyWhen / requiredWhen at parse.
  • Refused now, at submit (ADR-0137 D2): the same submit, with form.visibleWhenFaulted naming the field(s) and the rule — a stored BLANK visibleWhen included ("A blank predicate takes this path too, wherever one is already stored"). The field is still drawn (D3 unchanged). Nothing is written.
  • Refused now, at authoring (ADR-0137 D1) — @object-ui/types (minor): FormFieldSchema's field-rule triad refuses a predicate whose text is blank after trimming, with the spec's own sentence (EVALUATED_EXPRESSION_SOURCE_REQUIRED), as the spec already does on FieldSchema. The accepted SHAPE is unchanged (see the ExpressionWireSchema paragraph); gate keys keep the plain wire, and a blank gate stays "no gate" plus a one-time diagnostic.
  • Why: ADR-0137 D2 ("at submit time, a field-rule predicate that cannot be evaluated refuses the write and names the field and the rule"), as ruled: the client refuses only the rule no server evaluates, because its fail-open render direction is otherwise a silent grant. requiredWhen / readonlyWhen faults — a stored blank one included — are refused by objectql's D2 and stay the server's. ADR-0137 refuses "blank means no rule" for the triad (Alternatives), so a blank is refused at the first place that can see it: authoring (D1, objectui's own wire handled in the same round, as the ruling's execution text asks) or submit (D2). Applied after contract review 5915380177, finding 1.
  • Accepted residuals (pinned, not worked around): a visibleWhen reading previous cannot be evaluated on a CREATE form, so such a form is refused on every submit (ruled); and the wizard's cross-step gate binds no previous in either mode, so the same rule is refused at an EDIT wizard's final submit too (accepted by the seat under the ruling's refusal of option D; persistedRecord is deliberately not bound).
  • Widenings in the same diff (published surface added):
    • @object-ui/core (minor): resolveFieldRuleState returns a new faults member beside its three verdicts, and its type FieldRuleFaults is exported from the package root.
    • @object-ui/i18n (minor): the new key form.visibleWhenFaulted in all ten packs, so the exported TranslationKeys type gains a member.

What changed

Core — the report a submit path reads. resolveFieldRuleState fills faults from the onFault passback of the very evalFieldPredicate calls that draw the verdicts, so no refusal evaluates a predicate a second time. It reports all three rule kinds; which ones a path refuses on is the path's ruling. Left out, by construction: a rule the verdict did not need (short-circuited, never ran). A stored BLANK rule is IN it, with its [blank] reason — ADR-0137 D2's third state, not a spelling of "no rule". The stale "open question" prose in fieldRules.ts now points at ADR-0137 D2 / D3 / D5. Edited regions stay clear of PR #11208's two hunks in that file (its import line and the evalFieldPredicate evaluate call); git merge-tree of this branch with #11208's head 947b8d38b is clean.

The three submit paths, each reading faults.visibleWhen off the call it already makes:

  • form.tsx: the conditionallyHiddenFieldNames memo (every declared field's visibleWhen, the 6a449fc49 clear-on-hide input) now also returns the faulted names. handleSubmit refuses BEFORE react-hook-form validation (nothing the user types clears a broken rule), on the in-form banner and the outcome toast, and marks the tabs holding the fields. Every declared field is judged, not only drawn ones: a faulted visibleWhen never clears on hide, so its value reaches the payload whatever hides it. A section-divider row's visibleWhen (its section's layout gate) is excluded.
  • FormPage: findMissingRequired became findSubmitRefusals, one resolveRowState pass returning both the faulted rows and the missing ones; the fault is refused first. Hidden sections stay skipped, as the required check already skipped them: on this page visibility decides only what is drawn, so a row a hidden section keeps off screen cannot be shown by its own broken rule.
  • WizardForm: missingRequiredByStep became gateFinalSubmit, the same pass returning both; a fault lands the wizard on the first step holding such a field.

Q2 — the two blank-gate silencers, diagnosed (ADR-0137 D4), verdicts unchanged:

  1. ExpressionEvaluator.evaluateCelCondition answered true for a blank CEL source before anything could say so. It now reports through evalFieldPredicate's [blank] channel (the caller's onFault when given, else the deduped built-in warning) and still answers true in every mode, throwOnError included.
  2. hasDeclaredPredicate folded blank text to "no gate". It still does (objectui#3850 / 「空谓词」的第四种拼法:{ dialect: 'cel', source: ' ' }(source 只有空白)仍被判成已声明门 → disabled 侧仍永久置灰、执行入口仍拒执行(#3850 裁决枚举未覆盖) #3960), and now reports once through the same channel. Its locator is fixed (the question is key-neutral), so the dedupe is per blank spelling across the app — stated in its docblock.

Two existing pins asserted the silence and now pin the diagnosis instead, verdicts untouched: SchemaRenderer.disabledGateFaultDiagnostic.test.tsx (objectui#3862 empty shapes) and ActionParamDialog.test.tsx (blank param visible).

ExpressionWireSchema — D1 on objectui's own form wire, and no second wire type. FormFieldSchema's visibleWhen / readonlyWhen / requiredWhen are ExpressionWireSchema.superRefine(...) (module-private FieldRulePredicateWireSchema in form.zod.ts): the check hands the predicate's TEXT (the string, or an envelope's source) to stripImportedDefaults(EvaluatedExpressionInputSchema) from @objectstack/spec/shared and refuses with that schema's verdict and sentence, so neither the blank rule nor its message is restated. Only the text is judged, never the envelope, whose spec dialect enum is narrower than the wire. The refined union's arms ARE ExpressionWireSchema.options, by reference, and its parse output is the authored value unchanged — so the accepted SHAPE is the one wire type objectui#7530 ruled, and only a blank VALUE is taken out. That closes the submit-refusal trap: a blank cannot be authored, and one already stored is refused at submit. BaseSchema's visible / hidden / disabled, a field's view-level visibleOn and an option's visibleWhen keep the plain const. Two test instruments followed: imported-defaults-8317.test.ts lists the new spec crossing, and terminal-unknown-key-refusal-11073.test.ts rebuilds its OPEN twin from the union's own def (its z.union(options) dropped the union's check and read the triad's blank refusal as an accept-set move).

Docs: content/docs/guide/metadata-diagnostics.md and content/docs/plugins/plugin-form.mdx said a broken predicate never blocks a submit; both now describe the refusal, and metadata-diagnostics.md adds that a blank field rule is refused (at authoring by the form schema, at submit when stored) while a blank gate stays "no gate".

Pins

  • packages/core/src/evaluator/__tests__/fieldRuleFaults-8069.test.ts — the report per rule kind, the create-form previous residual with its edit-form control, short-circuited rules, a stored blank rule reported as [blank] for each rule kind with an absent-rule control, and both silencers with their controls (fresh module graph per case, since the unit project runs isolate: false).
  • packages/components/src/renderers/form/__tests__/visibleWhen-fault-refuses-submit-8069.test.tsx — the refusal, its ordering before validation, the healthy control, a stored blank visibleWhen refused, a blank view-level visibleOn NOT refused (the gate control), the divider exclusion, the residual and its edit control, and the end-to-end server pin: a faulted requiredWhen / readonlyWhen is let through by the client and the server's D2 envelope (VALIDATION_FAILED, code: 'rule_violation', constraint.reason: 'unevaluable', transcribed from objectql at the spec 17.5.0 tag 0f6dcac5e9) lands inside that field's data-field wrapper.
  • apps/console/src/components/FormPage.visibleWhenFault-8069.test.tsx and packages/plugin-form/src/wizardVisibleWhenFault-8069.test.tsx — the same cases on the page (read off the stubbed write) and on the gate (a skipped step, so only the gate can see the field).
  • visiblewhen-clear-on-hide-6958.test.tsx BOUNDARY pin restated for D2: a broken predicate clears nothing AND writes nothing — the verbatim constraint "a broken predicate must NEVER silently null a stored column" now holds in both halves.
  • packages/types/src/__tests__/base-schema-predicate-envelope-7530.test.ts — re-pinned to what objectui#7530 protects, ONE wire shape: BaseSchema's gates and the form's gate legs still carry ExpressionWireSchema itself; the triad carries a refined clone whose arms are ExpressionWireSchema.options by reference; each triad key refuses '', whitespace, { source: '' } and a whitespace CEL envelope at the key with EVALUATED_EXPRESSION_SOURCE_REQUIRED, accepts a non-blank string, a CEL envelope and a dialect-less envelope with the value unchanged, and still refuses junk; the control: a blank gate on BaseSchema, visibleOn and an option's visibleWhen still parses. The comment cites ADR-0137 D1 and this ruling.

Verification

All heavy runs went through os-verify-lock.sh (slot issue-8069); verdicts read from its VERDICT line and from the pass counts, never a bare exit code. Base 0389650f3.

Rework round (contract review 5915380177: finding 1 as option B, finding 2 accepted), final head 277476aaf:

  • At 277476aaf: apps/console/src/components/ 36 files, 437 passed; the twelve pin files (the four new ones, fieldRules.test.ts, the The Console submits the value of a field its own visibleWhen has hidden, and clears nothing — so visibleWhen on a populated lookup is a dead end, not a hint #6958 file, wizardPredicateScope, ActionParamDialog, SchemaRenderer.disabledGateFaultDiagnostic, and the types pins base-schema-predicate-envelope-7530, imported-defaults-8317, terminal-unknown-key-refusal-11073) 12 files, 469 passed; @object-ui/console type-check EXIT 0.
  • At 90d406183 (the one later commit changes only the console pin file, re-run above): turbo run build --filter='@object-ui/console^...' --concurrency=2 EXIT 0; type-check EXIT 0 for @object-ui/types, @object-ui/core, @object-ui/i18n, @object-ui/components, @object-ui/plugin-form, @object-ui/console, 0 error TS; packages/types/ 289 files, 6915 passed; packages/core/ 191 files, 3768 passed, 27 skipped; packages/components/src/renderers/form/ 69 files, 475 passed, 17 skipped; packages/plugin-form/ in three chunks, 50 + 47 + 57 files, 310 + 514 + 953 passed, 1 skipped; root scripts/__tests__/ 177 files, 5371 passed.
  • eslint on the 12 code files this round changed: 0 errors, per-file warning counts unchanged (0 on the new and changed pins). Light gates at 90d406183, all EXIT 0: the list below plus check:spec-symbols and check:phantom-deps; check-governed-queue-guard --test over 34 paths NOT GOVERNED.
  • Ablations at 277476aaf, same discipline as below: baseline 5 files, 112 passed. D — drop the triad's blank refusal (the superRefine's issue branch made unreachable): 12 failed / 50 passed, exactly the twelve blank cases (three keys, four spellings); every accept, shape-identity, junk and gate control green; blob 53a98c5b to c748e2ba and back. E — take a stored blank out of the fault report again: 7 failed / 43 passed, exactly the seven stored-blank cases (core four, form, FormPage, wizard); blob 4d2bb278 to 01b3a2c2 and back. The first D attempt, at 90d406183, was an anchor miss the tool refused (exit 3, nothing ran): its anchor was the pre-superRefine spelling.

First round, at ff6d7775b:

  • type-check (each after turbo run build --filter='@object-ui/console^...' --concurrency=2, EXIT 0): @object-ui/core, @object-ui/i18n, @object-ui/components, @object-ui/plugin-form, @object-ui/console — all EXIT 0, 0 error TS. Each package's script also compiles its tests (tsconfig.test.json, and the console's own config), which is where the first round found twelve TS7006 and one TS2550 in the new pins, all fixed.
  • pnpm exec vitest run packages/components/src/renderers/form/: 69 files, 474 passed, 17 skipped.
  • The eight pin files (the four new ones, fieldRules.test.ts, the The Console submits the value of a field its own visibleWhen has hidden, and clears nothing — so visibleWhen on a populated lookup is a dead end, not a hint #6958 file, ActionParamDialog.test.tsx, SchemaRenderer.disabledGateFaultDiagnostic.test.tsx): 8 files, 189 passed.
  • eslint on the 27 changed code files: 0 errors; every file's warning count equals its count at base (new files 0) — population is the changed-file list, counts from --format json, and type-aware linting is not enabled in eslint.config.js (no project / projectService), so the diff cannot move a verdict on an untouched file.
  • Light gates, all EXIT 0: check-changeset-presence, check-control-bytes, check:new-line-citations (0 new), check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check-changeset-claims, check-changeset-no-major, check:test-path-roots, check-vi-mock-specifiers, check-unreferenced-sources, markdown-test-inputs.mjs --audit. A control-byte scan of every changed file finds none.
  • check-readme-exports: NOT MEASURED in full — it exits 1 on two packages outside this diff that the build closure does not include (@object-ui/cli, @object-ui/plugin-ai, "run pnpm build first"); over the 35 built packages it judged 545 self-imports real, 0 wrong-path, 0 fabricated.

Earlier heads (the later commits only touched test typing, two pin files re-run above, and one (field as any).label becoming field.label):

  • packages/core/ 191 files, 3767 passed, 27 skipped · packages/i18n/ 74 files, 1216 passed · apps/console/src/components/ 36 files, 436 passed (all at 5800ae995).
  • packages/plugin-form/ in three chunks: 50 + 47 + 57 files, 310 + 514 + 952 passed, 1 skipped · packages/react/ 104 files · packages/components/src/renderers/action/ 29 files, 475 passed · 18 consumer files of hasDeclaredPredicate / resolveFieldRuleState in app-shell, plugin-detail, plugin-grid and components · root scripts/__tests__/ 177 files, 5371 passed (all at 2dc06d4e2). The react and consumer runs each had ONE red, both a pin asserting a blank gate stays SILENT — the behaviour Q2 = B reverses; both pins now assert the diagnosis and are green in the final run.
  • 20 more files that watch console.warn near a blank value, found by grep across other packages: 22 files, 773 passed (at cfd6676a9).

Ablations (at cfd6676a9, each leg through ablation-replace.mjs: the anchor hit exactly once, the write proven on disk by anchor count and blob hash, the restore proven by blob hash equal to HEAD and an empty git diff HEAD; the package is aliased to src in the vitest config, so no dist leg applies):

  • Baseline, unmutated: 6 files, 104 passed.
  • A — drop the visibleWhen refusal at its one source (the fault report records nothing): 13 failed / 91 passed, red by name on every refusal and report pin across all six files (form, The Console submits the value of a field its own visibleWhen has hidden, and clears nothing — so visibleWhen on a populated lookup is a dead end, not a hint #6958 boundary, wizard, FormPage, core); every CONTROL, blank, requiredWhen / readonlyWhen and edit-form case stayed green. Blob 2e74e4cc to dc5beddab and back to 2e74e4cc.
  • B — restore the silent blank fold (hasDeclaredPredicate without its report): 4 failed / 20 passed, exactly the four silencer-2 diagnosis cases; its controls stayed green. Blob 748c4f17 to 0fb13924 and back.
  • C — restore the silent evaluateCelCondition guard: the first attempt was a no-op the tool refused (its replacement contained the anchor, so the anchor count did not drop; exit 1 before anything ran). Re-run with a different anchor: 2 failed / 22 passed, exactly the two silencer-1 diagnosis cases; the throwOnError-no-throw case and the control stayed green. Blob bcf875ee to 24a1178d and back.

Acceptance notes

  • Other submit paths (dispatch item 3): ObjectForm and its drawer, modal, split and tabbed layouts, each wizard step, and MasterDetailForm's header ObjectForm all render a type: 'form' node, so they submit through form.tsx and inherit the refusal; a wizard step whose own field faulted therefore refuses its Next as well. RecordFormPage delegates to ObjectForm. Not judged: ScreenView (flow screens — not a record write), GridField (grid-column rules are presentation-only in the spec) and plugin-kanban's requiredWhenPrompt (requiredWhen only, which the server refuses).
  • FormPage shows a server D2 refusal as page-level text, not inline — it has no extractFieldErrors path. The server's message still names the field and the rule. No change here.
  • Not refused, by the ruling's scope: the view-level field predicate (visibleOn, and a form view's own visibleWhen, which sectionFields routes there) and SECTION predicates are layout gates, not field rules; they stay fail-open and warned.
  • Binding gaps the ruling declined to plumb (option D), each measured with zero producers in objectstack examples/: a field visibleWhen reading parent outside a grid, and one reading a column the create form does not render or seed, are unevaluable there and so refused.
  • Accepted residuals, side by side: a visibleWhen reading previous is refused on a CREATE form (ruled), and at the final submit of an EDIT wizard, whose cross-step gate binds no previous in either mode (accepted by the seat under the ruling's refusal of option D; persistedRecord is deliberately not bound).
  • Two more blank silencers outside the two named ones, left as they are: evaluateCondition's legacy path returns true for a bare '' / whitespace string, and evalRowPredicate returns its fallback for a bare blank string, both without a diagnostic. A third silent path, also a gate and also untouched: a blank VIEW-level field predicate is dropped by section normalization (sectionFields' attachVisibility) before any evaluator sees it, on both form chains — "no gate", never refused, but not diagnosed either.
  • A published skill sentence is now false and is a governed surface: skills/objectui/guides/schema-expressions.md says field-rule evaluation "never blocks submit". It is not edited here, so this PR stays off the governed surface; it needs its own PR.

Overlap

PR #11208 (objectui#4421) edits fieldRules.ts (its import block and the evalFieldPredicate evaluate call) and RecordFormPage.tsx's evaluator memo. This PR touches neither hunk and does not touch RecordFormPage.tsx; the textual merge is clean. PR #11208 landed first (9cebfca5a); the merge queue's merge group runs this PR's CI over main, which carries it.

Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec


Generated by Claude Code

…nk-gate silencers are diagnosed (objectui#8069)

ADR-0137 D2 needs a submit path to know which field rule could not be
evaluated. resolveFieldRuleState now returns `faults` beside the three
verdicts, filled from the same evaluation (the onFault passback), so a
refusal never re-evaluates. The verdicts are unchanged (D3). A blank
predicate stays out of the report: objectui's form wire admits it, so a
refusal on it would be a trap; it keeps its [blank] warning.

ADR-0137 D4: evaluateCelCondition's blank guard and hasDeclaredPredicate's
blank fold now report through evalFieldPredicate's [blank] channel. Both
verdicts (objectui#3850 / #3960) are unchanged, throwOnError included.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…e submit, naming the field and the rule (objectui#8069)

ADR-0137 D2 as ruled (Q1 = B, one judge per rule): the form renderer,
the console's FormPage and the wizard's cross-step gate refuse a submit
whose field visibleWhen could not be evaluated, with the new
form.visibleWhenFaulted message naming the field and the rule. Each
reads the fault from the resolveFieldRuleState call that already drew
the verdict. requiredWhen / readonlyWhen are unchanged on the client:
the server refuses their faults and form.tsx shows that inline. A blank
visibleWhen stays "no gate" with its [blank] warning, and a visibleWhen
reading previous on a create form is refused (the accepted residual).

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…lyWhen faults are the server's (objectui#8069)

Two sentences said a broken field predicate never blocks a submit; that
is no longer true for visibleWhen (ADR-0137 D2 as ruled).

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…i18n provider; the objectui#6958 boundary pin states D2 (objectui#8069)

The form renderer and the wizard translate through createSafeTranslation
tables; both now carry form.visibleWhenFaulted (and the wizard the list
joiner) byte-identical to the en pack. The #6958 BOUNDARY pin asserted a
broken visibleWhen writes the value as it stood; under ADR-0137 D2 the
write is refused, so it now pins both halves of "a broken predicate
never silently nulls a stored column": nothing cleared, nothing written.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…sis (objectui#8069)

ADR-0137 D4 (Q2 = B as ruled): a blank gate folded to "no gate" is
diagnosed, never silent. The verdicts these two pins hold (objectui#3862
enabled, objectui#3850 / #3960 kept) are unchanged; the "and silent" half
now reads "and reported once per blank spelling, through core's [blank]
channel, with no fault report".

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

Callback parameters over mock.calls are annotated (TS7006), and the
console pin reads the last toast by index (its lib has no Array#at).

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

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 25 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5905-componentinput-inputtype-tombstone.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    The write was measured as a no-op before it was deleted, and re-measured on this branch's base rather than inherited from the card. A structural census over every inputs: array in the repository (211 regions, all tracked TS/TSX/JS sources) scores inputType at exactly ONE authoring site — the plugin-markdown registration — against name 953, type 969, label 966, description 194, enum 119, required 86 and binding 4 in the same pass over the same regions, so the instrument was not blind. The other 192 in-repo inputType hits are a DIFFERENT face: FormField.inputType (zod/form.zod.ts), the text-input renderer's prop, and SchemaBuilder.inputType, none of which sit on a ComponentInput. The publication path is unchanged and was re-confirmed: packages/sdui-parser/src/index.ts forwards exactly seven keys per input — name, type, of, required, enum, binding, description — so an authored inputType could not reach the published sdui.manifest.json even in principle.

.changeset/6237-wizard-step-config-split.md

  • names WizardForm.tsx → packages/plugin-form/src/WizardForm.tsx — edited by this change

    WizardStepConfig is now declared independently in WizardForm.tsx, which is simply what SplitFormSectionConfig, ModalFormSectionConfig and DrawerFormSectionConfig already do: each layout owns its group shape, documents className / gridClassName in its own terms, and declares visibleWhen only where its renderer honours it. The derivation flips from subtractive to additive — a key is authorable on a wizard step only if someone writes it there.

  • names content/docs/plugins/plugin-form.mdx → content/docs/plugins/plugin-form.mdx — edited by this change

    Documentation repair in the same stroke: the support table in the README and in content/docs/plugins/plugin-form.mdx still said formType: 'tabbed' sections drop the predicate. That stopped being true when the tabbed arm landed — the row now reads Yes, the surrounding prose no longer claims two inert arms or a diagnostic that fires for tabbed, and the wizard row stays No, which is still exactly true.

.changeset/6349-name-authority-batch-3.md

  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    @object-ui/components — ComboboxOption now IS @object-ui/types' declaration. The component declared its own { value, label }, a strict subset of the ComboboxOption that @object-ui/types declares for ComboboxSchema.options and mirrors in form.zod.ts ({ value, label, disabled? }). The component now re-exports the types declaration (through the @object-ui/types/form subpath — the root barrel does not publish the name), so the name ComboboxOption exported from @object-ui/components gains the optional disabled?: boolean member. Every value that type-checked before still does — nothing narrows and no key changes type; the one thing that moves is keyof ComboboxOption, so a consumer that EXHAUSTS the type (a Record over its keys) will need the new key. Note that the Combobox component itself does not read option.disabled — that member was already declared on the @object-ui/types face and is now visible on this one too; it is recorded as a separate finding, not changed here.

.changeset/6396-previous-values-dom-leak.md

  • names packages/types/src/zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    Scope is the runtime leak only. The declared key stays exactly as declared (packages/types/src/form.ts, packages/types/src/zod/form.zod.ts are untouched): it has a live consumer, so there is nothing here for the enforce-or-remove channel.

.changeset/6444-evaluator-fault-warn-dedupe.md

  • names fieldRules.ts → packages/core/src/evaluator/fieldRules.ts — edited by this change

    This is the one-per-source rate limit both sibling reporters already carry (warnPredicateFailure in fieldRules.ts, visibilityDiagnostic.ts in @object-ui/react), not a third mechanism. The dedupe key is the predicate's authoring identity — the fault site plus the source text, never the scope it ran against — which is both the siblings' precedent and the defect itself: the 200-row flood is one authored source evaluated against 200 distinct scopes, so a scope-sensitive key would emit all 200 lines again.

.changeset/6505-predicate-valued-gate-rules.md

  • names evaluator/declaredPredicate.ts → packages/core/src/evaluator/declaredPredicate.ts — edited by this change

    The verdict is delegated to hasDeclaredPredicate (evaluator/declaredPredicate.ts), the repo's single definition of "is a predicate gate declared on this value?" (objectui#3850's ruling), rather than answered a second time in the validator — a hand-rolled twin that agrees today and drifts tomorrow is the defect class this rule was already an instance of. packages/core/src/validation/__tests__/predicate-valued-gate-rules.test.ts pins the delegation behaviourally: the rule's verdict must equal boolean || hasDeclaredPredicate(value) across every probe in the file.

.changeset/6661-app-launcher-nav-menu-renderers.md

  • names en.ts → packages/i18n/src/locales/en.ts — edited by this change

    Three new strings — the launcher's and the menu's accessible names, and the menu's empty state — are declared under console.nav in en.ts and its nine sibling packs. An inline defaultValue alone is not a fix: it renders English at one call site and leaves the string untranslatable everywhere (objectui#3517).

.changeset/6938-checkbox-wrapper-class.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    packages/components/src/renderers/form/checkbox.tsx:36 reads cn("flex items-center space-x-2", schema.wrapperClass) — classes on the wrapper div around the box and its label — and neither the TypeScript interface in packages/types/src/form.ts nor the zod mirror in zod/form.zod.ts declared the key. It compiled through BaseSchema's index signature and parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on FileUploadSchema and FilterBuilderSchema (objectui#6150); the checkbox was left out only because its doc page's schema block is a six-line summary.

.changeset/7113-chart-data-model.md

  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7530-predicate-envelope-declared.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.
  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.

.changeset/7722-wrapper-class-five-more.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (b74a8598d), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/7735-zod-mirrors-stop-authoring-defaults.md

  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    What changed. All 41 .default() call sites under packages/types/src/zod/ are removed — layout.zod.ts 22, crud.zod.ts 11, form.zod.ts 5, views.zod.ts 2, app.zod.ts 1. @object-ui/components reconciles the third face a separate finding found: flex's registration defaultProps.align seeded 'center', the value its own renderer never applies, so a designer-made node laid out differently from a hand-authored one; it now seeds 'start'.

.changeset/8069-field-rule-fault-direction-declared.md

  • names evaluator/declaredPredicate.ts → packages/core/src/evaluator/declaredPredicate.ts — edited by this change

    Both spellings now report [blank] the predicate is declared but empty — nothing to evaluate through the same single reporting site as every other fault, on both channels (the built-in console.warn and the onFault passback, so the fault-probing callers that pass warn: false are not silenced either). Every verdict is unchanged, including the envelope spelling: { source: '' } used to reach the engine and come back "AST-only evaluation not yet supported; persist source" and { source: ' ' } "Unexpected token: EOF" — two misleading reasons for one author mistake, both already resolving to the same fallback this change keeps. Blankness is decided by isBlankPredicateText (evaluator/declaredPredicate.ts), the repo's one definition of that question since objectui#3960, now exported for this second consumer rather than copied.

.changeset/8166-record-scope-data-root.md

  • names fieldRules.ts → packages/core/src/evaluator/fieldRules.ts — edited by this change

    The fault is loud, not fatal. The verdict a faulting predicate resolves to is unchanged: visibleWhen still fails OPEN, readonlyWhen / requiredWhen still fail permissive (@object-ui/core's fieldRules.ts; the direction is objectui#8069's open question, not this change's). So a record form renders exactly as before except that the console now names the root. Nothing throws.

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-form-layout.md

.changeset/8499-node-slot-registered-arms.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). ⚠️ Dated note, 2026-09-27 — that set has since gained code — objectui#10756. At this change TAGS and this arm both named 37 tags; both now name 38, and the parity pin counts 38. The rest of this entry is kept as the reading of this change. - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. ⚠️ Dated note, 2026-09-28 — inputType is now declared on this arm, as a refusal — objectui#8762. Later in this same release the arm declares inputType on both faces and refuses it by name (?: never on the TypeScript face, a retirementTombstone on the zod mirror, at path inputType), with guidance pointing at { "type": "input", "inputType": "email" }. So "inputType is deliberately NOT declared on this arm" no longer holds; the reason does, since the wrapper still spreads its own value last. The rest of this entry is kept as the reading of this change. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).

.changeset/9067-zod-barrel-named-arms.md

  • names zod/form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • InputShorthandSchema (zod/form.zod.ts) — the email / password shorthand arm. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers, a different component from the calendar plugin view that owns the bare literal.

.changeset/console-form-container-specs-one-declaration-5596.md

  • names FormPage.tsx → apps/console/src/components/FormPage.tsx — edited by this change

    objectui#5542 converged the LEAF of this contract — the field spec — and left the two containers above it untouched, because converging them was a bigger call than a mechanical import. FormSectionSpec and FormViewSpec were each hand-declared twice under the same names, once in packages/app-shell's SchemaForm.tsx and once in apps/console's FormPage.tsx. Unlike the leaf — whose console copy was a clean subset — these two had already drifted, in both directions, so neither copy was a subset of the other and there were two live answers to "what may an author write":

.changeset/console-form-field-spec-one-declaration-5542.md

  • names FormPage.tsx → apps/console/src/components/FormPage.tsx — edited by this change

    objectui#5040 was not a missing key. It was that two hand-written descriptions of one contract drifted, and nothing could notice, because each was only ever checked against itself. PR metadata-admin: FormFieldSpec declares dependsOn, one declaration for both halves #5537 converged the two app-shell descriptions into views/metadata-admin/form-spec.ts. A third survived in apps/console: FormPage.tsx declared its own nine-key interface FormFieldSpec, under the same name, in a different package — so the same failure mode stayed fully available.

.changeset/console-formpage-runtime-default-seed-5727.md

  • names apps/console/src/components/FormPage.tsx → apps/console/src/components/FormPage.tsx — edited by this change

    readPrefill in apps/console/src/components/FormPage.tsx seeded every declared default unconditionally. A defaultValue may be a literal, or an instruction the server resolves per insert — a DEFAULT_VALUE_TOKENS token (NOW(), current_user) or a CEL Expression envelope. Seeding one of those literally put the text NOW() into a datetime input on both /forms/:name and the public /f/:slug route, and submitting it sent that string as the field's value — which is neither absent nor null, so ObjectQL.applyFieldDefaults never resolved the declared default and the column stored the token text instead of a timestamp.

.changeset/console-formpage-visible-predicates-5594.md

  • names apps/console/src/components/FormPage.tsx → apps/console/src/components/FormPage.tsx — edited by this change

    apps/console/src/components/FormPage.tsx is a second, independent form renderer — its own buildSections, its own JSX — and it serves both the public /f/:slug route and the internal /forms/:name route. It read neither spelling of the FormView field visibility predicate: a repo-wide grep for a visibleWhen / visibleOn read inside that file returned zero. So a field an author conditioned on record.priority == 'urgent' — legal, spec-strict metadata that @objectstack/spec normalises to visibleWhen (ADR-0089), and that the metadata-admin designer both authors and honours — rendered unconditionally on both routes. Fail-open and silent: the author saw the field always, with no diagnostic.

  • names renderers/form/form.tsx → packages/components/src/renderers/form/form.tsx — edited by this change

    objectui#2212 recorded this exact symptom and PR fix(form): evaluate view-level FormField.visibleOn with the canonical CEL engine #2214 fixed it — in a different chain: ModalForm → resolveFormViewLayout → @object-ui/plugin-form sectionFields.ts → @object-ui/components renderers/form/form.tsx. FormPage.tsx is on that chain at no point, and Form-view FormField.visibleOn (CEL) is never evaluated — conditional fields always render #2212's regression pin lives with the chain it fixed, so nothing in the suite could see this copy. One contract, two implementations, each only ever checked against itself.

  • names FormPage.tsx → apps/console/src/components/FormPage.tsx — edited by this change

    objectui#2212 recorded this exact symptom and PR fix(form): evaluate view-level FormField.visibleOn with the canonical CEL engine #2214 fixed it — in a different chain: ModalForm → resolveFormViewLayout → @object-ui/plugin-form sectionFields.ts → @object-ui/components renderers/form/form.tsx. FormPage.tsx is on that chain at no point, and Form-view FormField.visibleOn (CEL) is never evaluated — conditional fields always render #2212's regression pin lives with the chain it fixed, so nothing in the suite could see this copy. One contract, two implementations, each only ever checked against itself.

  • names evaluator/fieldRules.ts → packages/core/src/evaluator/fieldRules.ts — edited by this change

    The wiring is Form-view FormField.visibleOn (CEL) is never evaluated — conditional fields always render #2212's ruling applied verbatim rather than a second predicate semantics invented for this renderer, because two form renderers disagreeing about what visibleWhen means would be a worse defect than one renderer ignoring it. The predicate goes through the canonical engine — evalFieldPredicate (@object-ui/core, evaluator/fieldRules.ts) — so the accepted wire shapes (bare CEL string and { dialect, source }), the bound scope (record.* = the live input values, previous.* = the stored record an edit form started from), and the fail-open-but-loud behaviour on an unevaluable predicate are the shared ones by construction. Resolution is canonical-first, visibleWhen ?? visibleOn, matching both sibling readers: sectionFields.ts and app-shell's readVisibility.

.changeset/evaluateexpression-jsdoc-links-5580.md

  • names ExpressionEvaluator.ts → packages/core/src/evaluator/ExpressionEvaluator.ts — edited by this change

    ExpressionEvaluator.ts declares two things spelled evaluateExpression: the method on ExpressionEvaluator (bare expression, throws) and the module-level export (context bag, fail-soft, delegating to evaluate). The registerFunction block referred to both under the one spelling, four lines apart.

.changeset/plugin-form-readme-classname-quantifier-5131.md

  • names packages/components/src/renderers/form/form.tsx → packages/components/src/renderers/form/form.tsx — edited by this change

    README.md's "Not a FormField key" table said a field-level className is "read on exactly one pseudo-field, type: 'section-divider'". That quantifier holds only for the renderer's explicit read — className={fp.className} on the section-divider branch of packages/components/src/renderers/form/form.tsx. The same renderer forwards every key it did not destructure, and className is not among the names taken off the field config, not among the ones stripRendererOnlyProps removes, and so rides {...fieldProps} into renderFieldComponent, whose built-in input branch spreads it onto ANGLE-BRACKETS(Input). A field-level className therefore lands visibly on ordinary built-in controls, and a reader taking "exactly one" literally concludes the opposite of what the code does (objectui#5131).

.changeset/text-input-description-aria-describedby-5735.md

  • names renderers/form/form.tsx → packages/components/src/renderers/form/form.tsx — edited by this change

    Before this the paragraph and the input were siblings with no programmatic relationship: a screen reader moving to the field announced the label and the value and never the helper text. The label half of the same block was already wired (htmlFor against the input's id), which is what made the gap specific to description rather than a general absence of a11y wiring — and the identical key authored on a field INSIDE renderers/form/form.tsx has been announced all along, so one authoring key behaved two ways depending on which container the author reached for. It no longer does.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with d0ae5d025 (merge-base with origin/main): 33 file(s) changed outside .changeset/, read against 1830 pending declaration(s) that publish a body (2440 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3577.4 KB 3607.4 KB
Main entry chunk (gzip) 149.7 KB 350 KB
Entry file index-C0CLwn7z.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) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
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) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
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.13KB 7.95KB
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) 569.74KB 136.33KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.19KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 242.01KB 63.55KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.92KB 44.37KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.44KB 15.11KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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 (expression.js) 0.20KB 0.18KB
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.26KB
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 (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-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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ff6d7775b62ce23919abddfa7d782d8dc22c4e93
Local-runs: none

Inputs: card #8069 (body and all 23 comments, the ruling 5910115531, claim 5912071659, amendment 5912804042, dev report 5915166902), PR #11233 (body, 30-file list, net diff vs main), the check-runs on the head (filter=latest), ADR-0137 at objectstack 0f6dcac5e9, and raw contents/ reads at the head for context (fieldRules.ts, declaredPredicate.ts, ExpressionEvaluator.ts, predicateInput.ts, the two index.ts barrels, form.tsx, FormPage.tsx, WizardForm.tsx, skills/objectui/guides/schema-expressions.md) plus PR #11208's fieldRules.ts patch for the overlap check.

Check-runs on the head: 43 runs, all completed at 2026-09-30T16:26Z — 40 success, 3 skipped (Test (coverage shard N/4), Test (coverage), dependabot — matrix placeholders and dependabot, not gates on this head), 0 failure, 0 in progress. The eight Test shards, Type Check, Lint, Build and E2E, Spec Main Shape Gate, the five changeset checks, Lint (which carries the i18n trio), Governed Surface Queue Guard, Line Citation Gate and the doc checks are all green; their conclusions are the gate verdicts read here.

① Derived judgments

Core — resolveFieldRuleState.faults (RIGHT). The report is filled by the onFault member of the diag(rule) closure handed to each of the three existing evalFieldPredicate calls, so it rides the same evaluation that draws the verdicts; nothing evaluates twice. A blank is kept out structurally: evalFieldPredicate at the head hands [blank] … to onFault like any other reason, and the closure's isBlankPredicateText(rules[rule]) guard drops it — decided by the repo's one blankness definition, never by reading the reason text. A short-circuited rule (static readonly, serverOwnedValue) never runs and so never reports (pinned). Return type widened to { visible, readonly, required, faults } and FieldRuleFaults reaches the package root through evaluator/index.ts export * and src/index.ts:56 — a published widening, named in ② below.

The three paths (RIGHT). form.tsx: the conditionallyHiddenFieldNames memo now also returns faultedVisibleWhenFieldNames off the very resolveFieldRuleState call the clear-on-hide path makes; every field carrying a non-null visibleWhen is judged (the memo's only skip is a field with neither visibleWhen nor visibleOn), section-divider rows are excluded, and the new handleSubmit wrapper refuses BEFORE form.handleSubmit validation — it is the only thing wired to the form's onSubmit (form.tsx:3356); banner, outcome toast and setRejectedFieldNames tab marks all fire. FormPage: findSubmitRefusals is one resolveRowState pass returning { faultedVisibleWhen, missing }, fault first. WizardForm: gateFinalSubmit is one pass over every non-hidden declared field, fault first, lands on the first step holding one. Named (field labels joined with validation.formInvalidJoiner, the rule in the message text) and localized: form.visibleWhenFaulted present in all ten packs (ar de en es fr ja ko pt ru zh) and in both createSafeTranslation defaults (form.tsx, WizardForm.tsx), byte-identical to en.

FormPage hidden-section carve-out (RIGHT by the ruling's rationale, narrower than D2's letter). A row inside a hidden section is not judged. On this page a hidden row's value submits unchanged whether or not its rule ran (no clear-on-hide), so a broken visibleWhen there changes neither what is drawn nor what is written — no grant to refuse. Named here because form.tsx judges every declared field for the opposite reason (its clear-on-hide makes a broken rule change the write). Pinned; acceptable.

requiredWhen / readonlyWhen unchanged on the client (RIGHT). The report carries them but no path refuses on them; each path pins a faulted one reaching the write. The end-to-end pin transcribes objectql's D2 envelope at the 17.5.0 tag (VALIDATION_FAILED, fields[] with code: 'rule_violation', constraint.reason: 'unevaluable') and shows it landing inside the field's data-field wrapper through the existing extractFieldErrors to form.setError path, with no page-level repeat. FormPage showing a server D2 refusal as page-level text is WITHIN the ruling: it ordered the refusal shown "through the existing path" and the client otherwise "unchanged"; FormPage has no inline path and building one would be new plumbing the ruling did not order. Acceptance note, correctly placed.

Q2 — the two gate silencers (RIGHT). Silencer 1: evaluateCelCondition now routes a blank source through evalFieldPredicate(source, {}, true, …) with { warn: false, onFault } when the caller passed onFault, else the fixed locator a CEL gate predicate, read as no gate; it still returns true in every mode, throwOnError included, and makes no engine call. Silencer 2: hasDeclaredPredicate still folds blank text to "not declared" and now calls reportBlankGate, the same channel with a fixed key-neutral locator — so the dedupe is per blank spelling app-wide (warnPredicateFailure keys a blank on [dialect, source, context]), documented in the docblock and pinned. The declaredPredicate.ts and fieldRules.ts cycle is SAFE: declaredPredicate.ts module scope is two imports and three function declarations, fieldRules.ts module scope is imports, one Set, constants and declarations; each side reads the other only inside a function body, predicateInput.ts imports nothing, and evalFieldPredicate asks isBlankPredicateText (not hasDeclaredPredicate), so reportBlankGate cannot recurse.

The ExpressionWireSchema trap (HALF RIGHT — see ③ and finding 1). No trap ships: a blank visibleWhen never enters faults, so no path refuses it, pinned in core and on all three paths. But the means — a stored blank field-rule predicate is never refused at submit on the client — is the opposite of ADR-0137 D2's blank sentence, and the PR writes that opposite into content/docs/guide/metadata-diagnostics.md and the changeset. That is a contract question the dev correctly raised and the seat's reading does not settle; it decides the verdict.

The residual pin (RIGHT, with one residual broader than the ruled one — finding 2). previous on a create form is pinned with an edit control in core (bound previous), form.tsx (mode: 'edit' + previousValues) and FormPage (recordId). The wizard pin has no edit control because the cross-step gate binds no previous in EITHER mode, so an EDIT wizard whose field visibleWhen reads previous is also refused at final submit — a refusal the ruling did not name.

Restated pins (each is what the ruling implies, none a weakening). SchemaRenderer.disabledGateFaultDiagnostic: from "silent" to two [blank] lines with reports(warn) still 0 and the objectui#3862 verdicts unchanged. ActionParamDialog: from "blank or absent — silent" to blank diagnosed (three spellings, three lines) plus a NEW control that an absent predicate stays silent. visiblewhen-clear-on-hide-6958 BOUNDARY: from "written as it stood" to "not cleared (the input still holds the value) AND not written (refused)" — the verbatim #6958 constraint now holds in both halves, D3 and D2.

Docs (RIGHT, one sentence contested). metadata-diagnostics.md and plugin-form.mdx no longer say a broken predicate never blocks a submit. The new paragraph's last sentence ("A blank predicate is not refused: it keeps its no-rule verdict") documents finding 1's deviation and moves with it.

Ablation legs (accepted on the report; not re-run — read-only). A (fault report records nothing) 13 red by name across all six pin files with every control, blank, requiredWhen/readonlyWhen and edit case green; B (silent fold) exactly the four silencer-2 cases; C (silent CEL guard) exactly the two silencer-1 cases after a first attempt the tool refused as a no-op. Each leg carries anchor count, blob before/after and a hash-equal restore. The head's check-runs are the gate verdicts.

Overlap with #11208 (disjoint, verified). #11208 merged at 2026-09-30T13:43Z (merge 9cebfca5a), after the claim's origin/main read at 0389650f3; this branch predates it and the head's fieldRules.ts shows no subjectPermissionsOf line. #11208's two hunks: the import block (base :75-81) and the evalFieldPredicate try-block evaluate call (base :257-276). This PR's hunks: :202-224 (@param prose), :301-325 (FAULTED docblock), :336-342 (the new FieldRuleFaults block), :365-371, :377-386 and :427-432 (resolveFieldRuleState). No overlap; GitHub reports the PR mergeable, state behind — it merges main before landing.

② Semver level

Changeset .changeset/8069-visiblewhen-fault-refuses-submit.md: @object-ui/core minor (the FieldRuleFaults export and the faults return member — a widening, RIGHT), @object-ui/i18n minor (new key, so TranslationKeys gains a member — RIGHT), @object-ui/components, @object-ui/plugin-form, @object-ui/console patch (behaviour narrowing declared through Clause-② and ## Narrowing, the repo's shape under check-changeset-no-major — RIGHT). @object-ui/react and @object-ui/app-shell carry test-only changes: no changeset needed. Changeset Bump Policy, Fixed Group, Declaration and Claim Re-read checks green.

Clause-②: the PR body reads Clause-②: yes (narrowing), the spelling the amendment 5912804042 reserves for a diff that widens AND narrows — this diff does both, so the line is RIGHT, and the claim comment's original yes (narrowing) turned out correct. ## Narrowing is present with Accepted-before / Refused-now / Why / the accepted residual, and names both widenings under "Widenings in the same diff" with their minor levels. If finding 1 is ruled the other way, the changeset's blank bullet and the docs sentence change with it.

③ Boundary flags

Under-listed surface (all within the ruling's scope). ExpressionEvaluator.ts is where silencer 1 lives and the ruling names it (Q2 = B, batch #119's evaluateCelCondition sentence). The two docs are AGENTS.md #2 — sentences the change falsified, corrected in the same PR. The three restated pins are the necessary consequence of the ruled behaviour: a pin asserting the old silence or the old write must move. The seat appends all five to the claim's surface.

Open question — a stored blank visibleWhen at submit (finding 1, decides the verdict). My reading: Q2 = B ("both blank-gate silencers are diagnosed and no verdict changes") is D4, about the two GATE silencers, and says nothing about a field-rule blank under D2; the seat's reading over-extends it. What speaks to a field-rule blank is (a) ADR-0137 D2: "A blank predicate takes this path too, wherever one is already stored — D1 keeps new ones from being authored, and D2 is what a stored one meets"; (b) ADR-0137's Alternatives, which refuse "blank means no rule" for the field-rule triad in so many words ("Q3 = yes is the ruling on that question"); and (c) the ruling's own execution text, "ExpressionWireSchema handled in the same round where a blank predicate WOULD BECOME a submit-refusal trap under B", which presupposes the refusal and asks the wire to be handled so the trap cannot be authored. Under Q1 = B the client is the only judge of visibleWhen, so the PR's choice means a stored blank visibleWhen is refused nowhere — declared by the protocol, enforced by no one, the defect ADR-0137 names as its own subject. The dev's option A is coherent (the spec side refuses a blank at authoring since 17.5.0; the objectui-native form wire is the only remaining author; zero blanks measured) and it is honestly flagged, but it is a decision on D2's semantics that neither the dev nor the seat holds. Resolution needed from the maintainer, protocol-first: EITHER rule A — then ADR-0137 D2's blank sentence needs an amendment on the spec card before this consumer documents the opposite; OR rule B — a stored blank visibleWhen is refused at submit like any fault, and FormFieldSchema.visibleWhen is narrowed to a non-blank wire in @object-ui/types under its own Clause-② (re-ruling objectui#7530's one-wire-type-by-reference). Until ruled, the PR's docs sentence, changeset bullet and the three "a BLANK visibleWhen is no trap" pins assert a contract position the protocol does not hold.

Residual broader than the ruled one (finding 2, non-blocking; the seat names it on the card). The ruling accepted "a visibleWhen reading previous on a CREATE form is refused (it is unevaluable there)". The wizard's cross-step gate binds no previous in either mode, so the same rule is refused on an EDIT wizard too, where the wizard holds persistedRecord and the rule is evaluable in principle. The dev files this under the ruling's refusal of option D ("no new binding pipelines for parent / previous / unrendered columns"), and "bind previous wherever the host holds the stored row" was indeed part of D — so it is within the ruling's letter. But it is a user-visible refusal the ruling's residual does not name (an edit wizard carrying such a rule can never complete), measured at zero producers in objectstack examples/. It goes on the card as an accepted residual in the seat's words, or to the maintainer as the one-line question whether passing the wizard's already-held row to the gate counts as a pipeline.

The skill sentence (confirmed false at this head). skills/objectui/guides/schema-expressions.md:309-311 reads: "Evaluation is fail-open -- a broken predicate never hides content, never blocks submit and never locks a field -- so visibleWhen is never a security boundary on the client." At ff6d777 a field-level visibleWhen that cannot be evaluated blocks the submit on all three paths, so "never blocks submit" is false and "never a security boundary on the client" is half-true (it now refuses the write). Governed surface, correctly not edited here; the seat's card is warranted and should land beside this PR, since a published skill teaching agents the opposite of shipped behaviour is the AI-proofing risk the ruling's axis ③ names.

Two more blank silencers (filing class: ONE successor card under ADR-0137 D4, not an acceptance note). At the head, evaluateCondition routes only a dialect: 'cel' envelope to the diagnosed guard; a bare string and an envelope WITHOUT dialect take the legacy path (ExpressionEvaluator.ts:361-377) and return a silent true for '', whitespace and { source: '' }. That is a GATE path SchemaRenderer's visibility legs reach raw, so D4's text ("a gate predicate that is blank or faulting is diagnosed, never a silent true") covers it; the ruling's Q2 named the two then known, so it is outside this PR. evalRowPredicate (listConditional.ts) returns its fallback silently for a bare blank string; a presentation predicate, same family. File one card carrying both, legacy path first (public door), dedupe words as the dev report gives them.

PR #11208 overlap. Verified disjoint in ① above; the PR is behind and merges main (now carrying #11208) before landing.

Findings, numbered:

  1. Contract deviation (blocking). A stored blank field visibleWhen is diagnosed and never refused at submit on the client, and content/docs/guide/metadata-diagnostics.md plus the changeset state that as the rule. ADR-0137 D2's blank sentence, its Alternatives section, and the ruling's own "would become a submit-refusal trap under B" all presuppose the refusal; Q2 = B does not decide it. Needs the maintainer's ruling (A with a spec-side ADR amendment first, or B with the refusal plus a @object-ui/types wire narrowing under its own Clause-②) before this head is adopted.
  2. Residual beyond the ruled one (non-blocking). An EDIT wizard whose field visibleWhen reads previous is refused at final submit because the cross-step gate binds no previous; within the ruling's option-D refusal, but unnamed by the ruling. The seat records it on the card as accepted, or asks the maintainer the one-line question.

Implemented-by: claude/issue-8069-visiblewhen-fault-refuses-submit
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: FAIL


Generated by Claude Code

… refused — at parse by the form wire (ADR-0137 D1), at submit when stored (D2) (objectui#8069)

Contract review 5915380177 (finding 1), as the seat applied it: ADR-0137
refuses "blank means no rule" for the field-rule triad. FormFieldSchema's
visibleWhen / readonlyWhen / requiredWhen now refuse a predicate that is
blank after trimming, with the spec's EVALUATED_EXPRESSION_SOURCE_REQUIRED
sentence, as a refinement OVER ExpressionWireSchema: the same two option
schemas by reference, so the one wire shape of objectui#7530 holds and
only the blank value is taken out. Gate keys keep the plain wire.

resolveFieldRuleState now reports a blank rule as a fault ([blank]), so a
stored blank visibleWhen is refused at submit on form.tsx, FormPage and
the wizard gate. The objectui#7530 pin is re-pinned to one wire SHAPE plus
the triad's blank refusal; the edit-wizard previous residual (finding 2)
is named; changeset and metadata-diagnostics.md follow.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…nused binding (objectui#8069)

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3577.3 KB 3607.4 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-DTIrD9BR.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) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
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) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
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.13KB 7.95KB
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) 569.73KB 136.32KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.19KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 216.08KB 44.49KB
plugin-detail (index.js) 242.01KB 63.55KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.92KB 44.37KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.46KB 15.12KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
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.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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 (expression.js) 0.20KB 0.18KB
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.26KB
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 (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-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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
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

…onInputSchema, not a restated rule (objectui#8069)

The form field-rule triad's check now hands the predicate TEXT to the
spec's own evaluated-slot schema (through the objectui#8317 import
boundary) and refuses with its verdict and sentence, so the rule and the
message are the spec's by reference. The import-boundary census lists the
new crossing; the objectui#11073 census rebuilds its OPEN twin from the
union's own def so a union-level check stays on both sides and only the
arms are measured. Two view-level controls pin "no gate, no refusal".

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3577.8 KB 3607.4 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-BFRmLgX9.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) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
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) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
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.13KB 7.95KB
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) 570.05KB 136.37KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.17KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 216.32KB 44.56KB
plugin-detail (index.js) 242.11KB 63.60KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.69KB 44.62KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.46KB 15.12KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
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.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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 (expression.js) 0.20KB 0.18KB
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.26KB
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 (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-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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
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

…e thank-you screen replaces the form (objectui#8069)

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3581.8 KB 3607.4 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-DPpLo8e0.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) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
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) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
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.13KB 7.95KB
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) 570.05KB 136.37KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.16KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 216.32KB 44.56KB
plugin-detail (index.js) 242.11KB 63.60KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.69KB 44.62KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 230.88KB 63.41KB
plugin-kanban (index.js) 48.46KB 15.12KB
plugin-list (index.js) 116.42KB 28.89KB
plugin-map (index.js) 23.50KB 7.82KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
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.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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 (expression.js) 0.20KB 0.18KB
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.26KB
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 (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-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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
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

@objectstack-fleet objectstack-fleet Bot changed the title fix(components,plugin-form,console,core): a faulted visibleWhen refuses the submit, naming the field and the rule; blank gates are diagnosed (objectui#8069) fix(types,components,plugin-form,console,core): a faulted or blank visibleWhen refuses the submit, naming the field and the rule; a blank field rule is refused at authoring; blank gates are diagnosed (objectui#8069) Sep 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 277476aaf1366db9b44794d4a7d93a56193f1d71
Local-runs: none

Round 2, after the FAIL record 5915380177 at ff6d7775b. Inputs: card #8069 (body and all 24 comments; the ruling 5910115531, claim 5912071659, amendment 5912804042, and the dev's follow-up report 5917486133 in full), PR #11233 (its relayed title and body, the 34-file list, the net diff vs main, and the inter-head diff ff6d7775b...277476aaf), the check-runs on the head (filter=latest), ADR-0137 at objectstack 0f6dcac5e9, issue objectui#7530's ruling, raw contents/ reads at the head (form.zod.ts, expression.zod.ts, imported-defaults.ts, the three packages/types test instruments and the base 7530 pin, fieldRules.ts, ExpressionEvaluator.ts, sectionFields.ts, the changeset, the skill guide, ObjectFieldInspector.tsx, CelPredicateField.tsx, PropertyEditor.tsx), and read-only git show of the spec's shared/expression.zod.ts and objectql's rule-validator.ts at the 17.5.0 tag.

Check-runs on the head: 43 runs, every one completed between 2026-09-30T18:09:48Z and 18:24:11Z, all on 277476aaf — 40 success, 3 skipped (Test (coverage), Test (coverage shard N/4), dependabot: matrix placeholders and dependabot, not gates on this head), 0 failure, 0 in progress. The eight Test shards, Type Check, Lint, Build and E2E, Spec Main Shape Gate, README Export Check, the five changeset checks, Governed Surface Queue Guard, Line Citation Gate and the doc checks are all green; their conclusions are the gate verdicts read here.

① Derived judgments

D1 on objectui's own form wire — ONE wire SHAPE, a blank VALUE taken out; not a second wire type (RIGHT; the fork-stop was right not to fire). form.zod.ts:963 defines module-private FieldRulePredicateWireSchema = ExpressionWireSchema.superRefine(...), mounted on the triad at :1000-1004; visibleOn (:998), SelectOptionSchema.visibleWhen (:83) and BaseSchema's gates keep the plain const. What objectui#7530 ruled (option A) forbids is a second ENVELOPE type — a copy that can drift in shape. A superRefine clone cannot: its .options IS the const's own array and each arm IS the const's own schema (pinned by identity: wire.options toBe ExpressionWireSchema.options, arms 0 and 1 toBe the const's, same constructor); the refinement neither transforms nor narrows the input or output type, so the parsed value is the authored one (pinned); the TS twin ExpressionWire is untouched, so twin parity holds; junk is still refused by the union's own issues before the check runs (pinned). The refinement judges only the predicate TEXT — the string, or an envelope's source — through stripImportedDefaults(EvaluatedExpressionInputSchema), so the spec's narrower dialect enum is never applied to the envelope and every shape #7530 admits still parses (pinned: string, CEL envelope, dialect-less envelope). The refusal carries the spec's own sentence, one issue at the key (pinned for '', whitespace, { source: '' }, a whitespace CEL envelope, on all three keys). No new export, so @object-ui/types gains no surface and loses a value set: a narrowing, nothing else. Producer side, measured: the one in-repo writer of the triad, ObjectFieldInspector.tsx (writePredicate, :424: a cleared value returns undefined), drops a blank rather than writing it; the designer's PropertyEditor.tsx carries no predicate key. stripImportedDefaults is memoized (imported-defaults.ts, a Map), so the per-parse call is a lookup.

D2 on all three submit paths, a stored blank included (RIGHT). fieldRules.ts:431-435: the diag closure now records EVERY onFault reason, so a stored blank enters faults as [blank] … under its rule (core pin: each of '', whitespace and a blank envelope on visibleWhen; readonlyWhen: '' and requiredWhen: { source: ' ' }; the absent-rule control reports nothing and warns nothing). The three paths read faults.visibleWhen unchanged from round 1 and so refuse a stored blank visibleWhen with the same form.visibleWhenFaulted message: pinned in form.tsx (no host call, toast, field still drawn per D3, [blank] warned), FormPage (no write, banner, field drawn) and the wizard gate (no create, lands on the step, names Owner). The gate controls beside each: a blank view-level visibleOn on the renderer is "no gate plus a diagnostic" and submits; a blank view-level section entry on the page and the wizard is dropped by sectionFields.attachVisibility and submits. requiredWhen / readonlyWhen stay the server's: the report carries their [blank] too and no path refuses on them (pinned). objectql at the tag (rule-validator.ts, the ADR-0137 D2 section): requiredWhen refuses on every fault, readonlyWhen on every fault but the unbound-root carve-out, and the file has no blank short-circuit, so a stored blank reaches the engine and is refused there — consistent with the body's sentence; not re-measured here.

The rest of the round-1 record stands at this head. The faults report and its no-second-evaluation property, the three paths' ordering, the ten packs plus two createSafeTranslation defaults, the server-envelope end-to-end pin, the two gate silencers and the benign declaredPredicate.ts / fieldRules.ts cycle, the restated pins, the #6958 boundary pin: none of those files changed this round except fieldRules.ts (the diag line and its docblock) and the three path pins, whose changes are the ones judged above. ExpressionEvaluator.ts at this head is byte-identical to the round-1 head.

Finding 2 of round 1 (the edit-wizard previous residual) — accepted and now NAMED (RIGHT). WizardForm.tsx's gate docblock says persistedRecord is deliberately not bound, under the ruling's refusal of option D; the changeset, the docs paragraph and the body's "Accepted residuals, side by side" carry it.

The two instrument corrections — honest fixes, not gates bent. (a) imported-defaults-8317.test.ts: its source census requires every @objectstack/spec VALUE read in a mirror to be the direct argument of stripImportedDefaults (form.zod.ts:967 is) and its differential requires every imported symbol to be a row of IMPORTED ("a schema imported by a mirror is not in this file's IMPORTED list … Add it"); the dev added exactly that row and nothing to either exception list. The new row carries no default, so its stripped and raw faces are the same object and the accept-set differential is trivially equal — which is what the instrument is for. (b) terminal-unknown-key-refusal-11073.test.ts: the differential compares each plain union with a strict arm against an OPEN twin whose closed arms are rebuilt as ordinary objects; the twin used to be z.union(options), which drops the union's own checks, so on the strict authoring face (where the triad's envelope arm is closed) the real union refused the corpus probe '' while the twin accepted it and the instrument read a union-level refinement as an ARM-level accept-set move. Rebuilding the twin from the union's own def keeps the refinement on both sides and the differential measures the arms alone, which is its stated question; the lit control (errorShapeMoves above 0) and the discriminating self-test (an open twin IS reported open) are untouched. Both fixes measure more truly than before.

Ablation legs D and E (accepted on the report; not re-run — read-only). D (the triad's blank refusal made unreachable): 12 red, exactly the twelve blank cases, every accept, identity, junk and gate control green. E (a stored blank taken out of the report): 7 red, exactly the seven stored-blank cases. Each with anchor count, blob before and after, and a hash-equal restore; the first D attempt was refused by the tool as an anchor miss before anything ran.

Every relayed body sentence and the title, checked at this head — TRUE. The ten replacements are in the body as the dev wrote them; each was checked against the head files: the ## Narrowing bullets (before/after at parse and at submit, D1 with the spec's sentence, gate keys on the plain wire, the two residuals); "a stored BLANK rule is IN it, with its [blank] reason"; the ExpressionWireSchema paragraph (module-private name, the text-only judgment, .options by reference, unchanged output, the plain const on the gate legs, the two instruments); the docs sentence; the three pin descriptions; the rework verification section (reported, and the head's check-runs agree); the binding-gaps bullet and the side-by-side residuals; the third silent path. Two sentences are commitments rather than facts and are named as such below: "git merge-tree … is clean" (a past measurement) and "whichever lands second merges main".

② Semver level

Changeset: @object-ui/types minor — a narrowing of a published schema's accept set with no new export, declared minor because this repository never marks major (check-changeset-no-major) and declares narrowings through Clause-② and ## Narrowing instead — RIGHT, and its own paragraph in the changeset names the three keys, the four refused spellings, the spec sentence, the unchanged shape and the gates that stay wide. @object-ui/core minor and @object-ui/i18n minor unchanged from round 1 (the FieldRuleFaults export and faults member; the new key). components, plugin-form, console patch (behaviour narrowing, declared). react, app-shell test-only. Changeset Bump Policy, Fixed Group, Declaration, Claim Re-read and Overwrite Report green on the head.

Clause-②: yes (narrowing) on the PR body: RIGHT — the diff still widens (core, i18n) and now narrows twice (a faulted or stored-blank visibleWhen at submit; a blank triad key at parse), and ## Narrowing names the @object-ui/types narrowing with its level beside the two widenings. The claim's line needs no change.

③ Boundary flags

The seat's option-B decision and its fork-stop — recorded in the dev report and the PR body, not on the card. The card's thread ends with the dev's follow-up report; the decision under which @object-ui/types narrowed ("finding 1 taken as option B per the seat", the fork-stop against objectui#7530) appears nowhere in the seat's own words. It is within the ruling's execution text ("objectui's own ExpressionWireSchema handled in the same round where a blank predicate would become a submit-refusal trap under B"), so no new ruling was needed — but the card should carry one line from the seat saying so, with the fork-stop's condition and the reading that it did not fire, so the thread explains the narrowing without the PR.

Card the seat must file, A — the governed skill sentence. skills/objectui/guides/schema-expressions.md:309-311 at this head reads, unchanged: "Evaluation is fail-open -- a broken predicate never hides content, never blocks submit and never locks a field -- so visibleWhen is never a security boundary on the client." At 277476aaf a faulted or stored-blank field visibleWhen blocks the submit on all three paths, so "never blocks submit" is false and "never a security boundary on the client" is now half-true. Governed surface, correctly untouched here; it needs its own PR and should land beside this one, because a published skill teaching agents the opposite of shipped behaviour is the AI-proofing risk the ruling's axis ③ names. Evidence: the file is not in this PR's 34 paths and is byte-identical to main at the lines above.

Card the seat must file, B — the ADR-0137 D4 successor, now with THREE silent blank-gate paths. D4 says a blank or faulting GATE predicate is "diagnosed, never a silent true"; the ruling's Q2 named the two silencers then known and this PR diagnoses both. Left silent at this head, each with its door: (1) ExpressionEvaluator.evaluateCondition (:361-377, unchanged this round) routes only a dialect: 'cel' envelope to the diagnosed guard; a bare string and a dialect-less envelope take the legacy path and return true for '', whitespace and { source: '' } with no diagnostic — reached raw from SchemaRenderer's visibility legs. (2) listConditional.evalRowPredicate returns its fallback for a bare blank string, silently. (3) plugin-form/src/sectionFields.ts:127-135 attachVisibility drops a blank view-level predicate (string or envelope source) before any evaluator sees it, on every chain that reaches it: :333 (meta.visible_on ?? meta.visibleOn), :343 (fd.visibleOn) and :430 (fd.visibleWhen ?? fd.visibleOn, a form view's own field predicate) — "no gate", never refused, never diagnosed; this PR's page and wizard controls pin that verdict, so the card inherits two pins to restate. One more line for that card, not a defect of this PR: objectui's gate mirrors stay wider than the spec's D1 population on purpose — SelectOptionSchema.visibleWhen (form.zod.ts:83, overriding the spec's narrowed key), FormFieldSchema.visibleOn (:998) and BaseSchema's three gates all admit a blank the spec's SelectOptionSchema / form slots refuse since 17.5.0. No trap follows (nothing refuses a blank gate at submit), so the ruling did not ask for them; whether the mirrors should follow D1 is that card's question. Dedupe words as the two dev reports give them.

Landing condition, not a finding. compare main...277476aaf: diverged, ahead 11, behind 28, merge base 0389650f3 — the branch does not yet carry #11208's merge (fieldRules.ts at the head has no subjectPermissionsOf line), and the check-runs above ran on that head. GitHub reports the PR mergeable, state clean (no conflict; the two PRs' fieldRules.ts hunks were verified disjoint in round 1). The body's own "whichever lands second merges main" therefore falls to this PR before it lands.

Round-1 items closed this round. Finding 1 (the blank at submit): resolved as option B, judged RIGHT in ① — a stored blank is refused at submit (D2) and a new one at authoring (D1), a blank gate stays diagnosed-not-refused (D4), and ADR-0137's Alternatives are now honoured for the triad. Finding 2: accepted and named. The under-listed claim surface (five items) still needs appending to the claim by the seat; packages/types/src/zod/form.zod.ts now joins it (the claim named expression.zod.ts; the narrowing was placed one file over, on the triad alone, which is the narrower and better placement).

No numbered findings: none of the items above blocks this head.

Implemented-by: claude/issue-8069-visiblewhen-fault-refuses-submit
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 19:02
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit af9e957 Sep 30, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-8069-visiblewhen-fault-refuses-submit branch September 30, 2026 19:17
huangyiirene pushed a commit that referenced this pull request Sep 30, 2026
…age and two console pins name reference

PR #11233 has landed, so the remaining tail is in scope: the form-field zod
comment, FormPage's metadata comment, plugin-form.mdx's field-slot row and the
shared-field-resolver pin now spell the target reference. deriveRelatedLists'
docblock drops the reference_to it has not read since objectui#6837 half 2.

Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
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.

Every field-rule predicate falls back to the PERMISSIVE verdict — one typo widens the form in all three directions at once

2 participants