fix(console): omit a cleared server-owned field from a create submit - #6060
Merged
yinlianghui merged 1 commit intoAug 24, 2026
Merged
Conversation
A field whose declared defaultValue is an instruction the server resolves per insert opens EMPTY on a create form since #5727, and its key stays out of `values`. But `handleSubmit` submitted `values` wholesale, and a control the user touches writes back through its `onChange` — so typing into such a field and then clearing it put the key back holding a blank. `ObjectQL.applyFieldDefaults` resolves a declared default only for a field arriving absent or null, so that blank was stored and the declaration silently defeated: the same suppression #5727 closed, reached by the other door. `omitServerOwnedBlanks` drops those keys from a CREATE payload on both routes. It composes the two canonical classifiers from `@object-ui/core` — `isRuntimeDefault` (already read here for #5727's seeding fence) and `isMissingForRequired` — rather than re-deriving either, so "server-owned" and "left empty" mean the same thing here as on the plugin-form chain. Emptiness is the shared PRESENCE predicate because the arms disagree about what a cleared control writes: the string arms write '', the number family writes null, and `false` stays a real value. Boundaries pinned by counter-probes: a blank cleared from a field with no runtime default (or a static one) is still submitted, and an edit submit is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Aug 24, 2026
yinlianghui
marked this pull request as ready for review
August 24, 2026 15:15
yinlianghui
deleted the
claude/issue-5883-formpage-omit-server-defaults
branch
August 24, 2026 15:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5883
The mechanism
Since #5727 a create form on the console's two form routes correctly opens a runtime-default control empty and leaves the key out of
values. ButhandleSubmitsubmittedvalueswholesale, and a rendered control writes back through itsonChangethe moment the user touches it — so a submitter who types into a server-owned field and then clears it puts the key back, holding a blank.ObjectQL.applyFieldDefaultsresolves a declared default only for a field arriving absent or null. A blank string is neither, so submitting one stores''and silently defeats the declaration: the same suppression #5727 closed, reached by the other door.Reproduced before fixing — the card was explicitly not browser-verified, so its reachability claim was not inherited.
FormPage.serverDefaults.test.tsxat the pre-fix commit: 5 failed, 4 passed (9), the four passes being the counter-probes, which are guards on both sides of the change.The fix
omitServerOwnedBlanksinFormPage.tsxdrops those keys from a CREATE payload on both the internal/forms/:nameand the anonymous/f/:slugroute. Theredirectsubmit behaviour's token scope moves fromvaluesto the same payload, since "the values as submitted" is what that scope has always claimed to be and the two now differ.The classifiers are not re-implemented. Both come from
@object-ui/core:isRuntimeDefault(already imported in this file for #5727's seeding fence) andisMissingForRequired. That is where they live precisely so every layer deciding server-ownership reads one answer, and it is howplugin-kanbanand@object-ui/components' form renderer read them too.Delta from the card: the sibling helper is not importable
The card and the dispatch both assumed the shape question was "adapt the console's data to
omitServerResolvedDefaults, or adapt its inputs". Measured on the merge-base, neither is available:@object-ui/plugin-form'sexportsmap publishes.alone, and its rootsrc/index.tsxdoes not re-exportschemaDefaults— soomitServerResolvedDefaultshas no spelling an importing package can write. Reaching it would mean editing another package's published surface, outside this card's file surface. Recorded as a separate observation in #6059, which is left open for triage and is not addressed by this PR.omitServerResolvedDefaultsitself is only a local application of the same two@object-ui/corecalls, so the thing that must not fork has not forked.What "empty" means, measured rather than assumed
A filter testing for
''would have left arms behind. Measured on this renderer's own controls, submitting after a touch-then-clear on each:string ""object null— the arm spellse.target.value === '' ? null : Number(...), so noNaNis reachablestring ""boolean false— a real value, not a clearBoth reachable empty spellings are inside
isMissingForRequired, andfalsecorrectly is not — which is exactly why the fix reads that predicate rather than testing for a blank string.Boundaries, each pinned
Verification
Direction predicted before running: the positive assertions go red on the submitted payload, the counter-probes stay green.
Ablation — call site reverted to
const payload = valuesat5bd1ee015, mutation proven on disk by grepping the injected marker (1 hit) and separately the removed text (0 hits), landing site printed and anchor uniqueness asserted before writing; restored undertrap ... EXIT INT TERMwith a cwd-independentgit -C /abs/worktree checkout -- /abs/worktree/apps/console/src/components/FormPage.tsx:Tests 5 failed | 4 passed (9)— every positive assertion red, every counter-probe greenTests 9 passed (9), injected marker 0 hits, removed text back,git diff HEAD --statemptyNo rebuild step sits between the mutation and the measurement: the console's vitest run aliases workspace packages to their
src/, and the mutated file is the console's own source.Gate union, run after the final commit, at
5bd1ee015:pnpm --filter @object-ui/console type-check(hyphenated; script name echoed as> @object-ui/console@17.6.0 type-check)pnpm exec vitest runover 14 console form suitesTest Files 14 passed (14)·Tests 236 passed (236)eslinton the two changed files (plain, no--no-inline-config)21 problems (0 errors, 21 warnings)— base measured at 20; delta is +1react-refresh/only-export-components, the warning each of this file's 12 existing exported helpers already carriesnode scripts/check-phantom-dependencies.mjsEvery in-scope import is declared by the package that publishes it.node scripts/check-changeset-presence.mjs2 source file(s) ... declares 1 changeset(s)node scripts/check-control-bytes.mjsOK (scanned 5000 tracked text file(s))check-changeset-no-major/check-changeset-fixed/check-lint-coverage/check-type-check-coverageThe first
type-checkrun was the unbuilt-closure red (Cannot find module '@object-ui/...') rather than a missing-node_modulesone; fixed bypnpm --filter '@object-ui/console^...' build, not by reinstalling.Generated by Claude Code
Generated by Claude Code