feat(console): settings validation errors render against the fields that caused them (objectstack#4224 follow-up) - #3083
Merged
Conversation
…hat caused them (objectstack#4224 follow-up) A `SETTINGS_VALIDATION` rejection names the offending keys, and this page threw all of it away — every failure collapsed into one toast carrying the server's summary sentence, with nothing marked on the inputs. On a namespace with a dozen keys the user was told a value was wrong and left to find which. That was not the console's fault. The server sent `fields` as a `Record<key, message>` hung BESIDE `error.code`, a position `ApiErrorSchema` never declared; it survived only because the schema is a plain `z.object` that strips undeclared keys instead of rejecting them. `extractFieldErrors` reads arrays (`details.fields` / `fields` / `validationErrors`), so a map at an undeclared position matched nothing and returned null. objectstack#4224 moved it to `error.details.fields` as the declared `FieldError[]` — which is what makes this wiring a few lines rather than a parser. - `SettingsField` gains an `error` prop, rendered in the slot the help text occupies rather than stacked under it: the two say the same kind of thing, and a description sitting below a red error reads as two states at once. - The mark is on the CONTROL, not just beside it — `aria-invalid` plus `aria-describedby` pointing at a `role="alert"` message. Colour is not a cue for everyone. Wiring it in `wrapper` covers every input type at once. - Cleared per-field the moment that field is edited (the error describes the value the server saw, so it contradicts what is in the box), and wholesale on Discard, on reload, and on a successful save. - Fields the server did not name stay unmarked, and a failure carrying no field array (500, unknown namespace) behaves exactly as before. The toast still fires alongside the marks: the offending field can be scrolled out of view or hidden behind a `visible` expression, and a save that appears to do nothing is the worse failure. Five tests drive the real post-#4224 wire body rather than a hand-tuned fixture, so a server regression to the map turns them red instead of passing against a shape nobody sends. Verified to fail without the wiring. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv2NYMBTrzVVRWiWWuyec5
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 31, 2026 02:04
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.
Follow-up to objectstack-ai/objectstack#4224 (merged) and objectui#3079 (merged) — the piece that change unblocked.
What was broken
A
SETTINGS_VALIDATIONrejection names the offending keys. The settings page threw all of it away: every failure collapsed into one toast carrying the server's summary sentence, with nothing marked on the inputs. On a namespace with a dozen keys, the user was told a value was wrong and left to find which.Why it was not the console's fault
This is the part worth recording, because it explains why the fix is small.
The server sent
fieldsas aRecord<key, message>hung besideerror.code— a positionApiErrorSchemanever declared. It survived only because that schema is a plainz.objectand strips undeclared keys rather than rejecting them, so nothing on either side of the wire ever flagged it.extractFieldErrorsreads arrays:A map at an undeclared position matched none of those and returned
null. objectstack#4224 moved it toerror.details.fieldsas the declaredFieldError[]— the second branch above — which is what makes this a few lines of wiring rather than a parser.The change
SettingsFieldgains anerrorprop, rendered in the slot the help text occupies rather than stacked under it. The two say the same kind of thing, and a description sitting below a red error reads as if the field has two states at once.aria-invalidplusaria-describedbypointing at arole="alert"message. Colour is not a cue for everyone. It's wired inwrapper, the shared layout every input type already goes through, so all of them are covered at once instead of case by case.The toast still fires alongside the marks, deliberately: the offending field can be scrolled out of view or hidden behind a
visibleexpression, and a save that appears to do nothing is the worse failure.Verification
pnpm --filter @object-ui/console test— 16 files, 116 tests green (5 new)extractFieldErrorscall turns 4 of 5 red. The fifth stays green by design: it asserts that a failure without a field array marks nothing, which must hold either way.tsc --noEmit— no new errors. Two appear in this environment only because@object-ui/reactisn't built here (Cannot find module, plus the implicit-anyit cascades into); both disappear afterpnpm --filter @object-ui/react... build, and CI builds packages first.Not in scope
FieldErroralso carrieslabelandconstraint(e.g.{ pattern }). This renders the server'smessageverbatim, which is already localized. Composing client-side text fromconstraintis a separate call — worth making once there's a reason to diverge from what the server writes.🤖 Generated with Claude Code
Generated by Claude Code