ui: a line-item dialog marks its required fields and names the one the server refused (#7062) - #7066
Merged
Merged
Conversation
…e server refused (#7062) The generated "Add line" dialog said nothing about which values were mandatory before the save, and nothing about which one was refused after it: a line missing a required relation came back as the generic banner "The request could not be completed. Please review your input." with no column marked, while the server had answered 400 "The 'TaxRate' property is required". Two halves, both in the dialog. The marker: `editColumns[].required` was already in the detail registration and simply never rendered - the label was a bare x-text, so only the header form marked its required fields. All three item dialogs (power document, personal, partner) now carry the same `*`. The mapping: the generated controllers state a rejection in prose, naming the property in single quotes, as a plain 400 rather than a structured 422 with errorCauses - so `messageFor` had nothing but the status to work from. Two shared helpers read that prose: `apiErrors.namedProperty(err, knownNames)` returns the first quoted token that IS a field of the form in hand (the gate that keeps an arbitrary developer-facing 400 out of the UI - anything else still falls back to the catalog message), and `messageWithLabels` rewrites the message in the surface's own labels ("The 'Tax Rate' property is required"). The dialog marks that column with aria-invalid - what Harmonia actually colours the label and the border from - and shows the message; `baseFormPage.applyApiError` does the same for every generated header form, so the two surfaces answer a named rejection alike. IntentEmissionCoverageIT guards the emitted marker, the aria-invalid binding and the mapping on the Roster document, whose line carries a required column. Fixes #7062
The six files under modules/parsers/typescript are ANTLR output checked into the repository; a local full build regenerates them, and they were swept into this branch's commit. They are unformatted regenerated output, so formatter:validate failed the code-style job on a change that has nothing to do with the TypeScript parser. Restored to master's versions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 8, 2026
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 #7062
What was wrong
The generated Add line dialog of a document said nothing about which values were mandatory before the save, and nothing about which one was refused after it.
Filling a line but leaving a
required: truerelation empty, the server answers a plain 400{"message":"The 'TaxRate' property is required"}- correct and specific. The dialog showed the generic banner "The request could not be completed. Please review your input.", marked no column, and carried no required marker on any field: the user was told the wrong field was wrong and never told which one was right.Two halves, both in the dialog
The marker.
editColumns[].requiredwas already in the detail registration and simply never rendered - the dialog's label was a barex-text, so only the header form marked its required fields. All three item dialogs (power document, personal, partner) now carry the same*the header form does.The mapping. The generated controllers state a rejection in prose, naming the property in single quotes, as a plain 400 rather than a structured 422 with
errorCauses- soapiErrors.messageForhad nothing but the status to work from and printed theBadRequestcatalog line. Two shared helpers now read that prose:apiErrors.namedProperty(err, knownNames)- the first quoted token that is a field of the form in hand. That gate is what keeps the developer-facing rule intact: an arbitrary 400 (a stack-trace reason, an internal identifier) names nothing known and still falls back to the catalog message.apiErrors.messageWithLabels(err, labels)- the same message in the surface's own labels: "The 'Tax Rate' property is required".The dialog marks that column with
aria-invalid- which is what Harmonia actually colours the label and the border from (has-[[aria-invalid=true]]:text-negativeonx-h-field,aria-invalid:border-negativeon the control) - and shows the message in its banner.baseFormPage.applyApiErrordoes the same for every generated header form, so both surfaces answer a named rejection alike instead of only the 422 path being wired.Scope note
I could not reproduce why
Namespecifically turned red in the report: nothing in the templates binds an error to it, and Harmonia colours a field only fromaria-invalidor a native:user-invalid, so that was most likely a stray native validity on that input rather than the error mapping. What this change guarantees is that the column the server named is the one explicitly marked; nothing else in the dialog is marked by generation.Verification
IntentEmissionCoverageITextended with assertions on the emitted marker, thearia-invalidbinding and the mapping, anchored on theRosterdocument (its line'sdayisrequired: true) - green, 1/1, 136.7 s, on top of a fullquick-buildreactor.formatter:validateclean on the touched module.