feat(10-03): open, edit and save a record with toast and 422 feedback

- The SPA loads the backend::lang bundle before /auth/me, sets the
  document language from meta.locale and renders plural messages with
  Intl.PluralRules; interpolate mirrors phrasebook for :name/:Name/:NAME
- Create and record routes; mapWinterUrl maps recordUrl and redirects
  onto the controller's list, create and record routes only
- List rows open their record; FormView loads the form schema and the
  record, shows context-allowed fields in the span grid, saves values
  keyed by field name and toasts the resolved saved message
- A 422 puts each message under its field (aria-invalid,
  aria-describedby), shows the plural banner, focuses the first invalid
  field in schema order and clears a field's error on change
- The D-05 registry maps text, textarea, number and dropdown; any other
  type renders the unsupported-field box with the type in DM Mono
- The admin OpenAPI document declares the write request bodies
  (AdminRecord, AdminIDsRequest) and the list filter query as a
  deepObject, so the typed client can send them
- New backend::lang form.load_failed key; boardwalk/dist rebuilt
This commit is contained in:
Jakub Zych
2026-09-27 16:43:47 +02:00
parent 453b8ae786
commit 126ca5b8ed
41 changed files with 1930 additions and 37 deletions

View File

@@ -66,6 +66,12 @@ type ListEnvelope[T any] struct {
// list or form schema (D-16).
type AdminRecord map[string]any
// AdminIDsRequest is the body of the id-list writes: bulk delete, relation
// link and relation unlink.
type AdminIDsRequest struct {
IDs []uint64 `json:"ids"`
}
// AdminLoginRequest is the admin login body. Either login or email
// identifies the backend user.
type AdminLoginRequest struct {
@@ -220,6 +226,7 @@ func AdminSettingsGet() {}
// @Produce json
// @Security BackendBearer
// @Param code path string true "Settings code"
// @Param body body AdminRecord true "Setting values keyed by field name"
// @Success 200 {object} Envelope[SettingsResult]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
@@ -331,6 +338,7 @@ func AdminFilterOptions() {}
// @Param dir query string false "Sort direction (asc or desc)"
// @Param page query integer false "Page"
// @Param per_page query integer false "Records per page"
// @Param filter query object false "Filter values keyed by filter name, sent as filter[<name>]=<value>"
// @Success 200 {object} ListEnvelope[[]AdminRecord]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
@@ -350,6 +358,7 @@ func AdminList() {}
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param body body AdminRecord true "Field values keyed by field name; relation fields carry ids"
// @Success 201 {object} RecordEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
@@ -368,6 +377,7 @@ func AdminCreate() {}
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param body body AdminIDsRequest true "Record ids"
// @Success 200 {object} Envelope[BulkResult]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
@@ -405,6 +415,7 @@ func AdminShow() {}
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Record id"
// @Param body body AdminRecord true "Field values keyed by field name; relation fields carry ids"
// @Success 200 {object} RecordEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
@@ -481,6 +492,7 @@ func AdminRelationCandidates() {}
// @Param controller path string true "Controller"
// @Param id path integer true "Owner id"
// @Param name path string true "Relation name"
// @Param body body AdminIDsRequest true "Related record ids"
// @Success 200 {object} Envelope[RelationMutationResult]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
@@ -501,6 +513,7 @@ func AdminRelationLink() {}
// @Param controller path string true "Controller"
// @Param id path integer true "Owner id"
// @Param name path string true "Relation name"
// @Param body body AdminIDsRequest true "Related record ids"
// @Success 200 {object} Envelope[RelationMutationResult]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope