Repository navigation
Schema: validation-rule editor + client-side row validation #19
Description
Activity
- addedparityWeb/API feature-parity workWeb/API feature-parity workP1Table stakesTable stakesarea:listsArea: listsArea: lists
on Sep 15, 2026 - added a parent issue
on Sep 15, 2026 Live-verified findings from the schema service (#17 / PR #143) — three change the design here
⚠️ 1. The non-destructivepropertiesform accepts only SIX types, not twelve400 Unknown propertyType 'textarea'. Allowed: text, number, boolean, date, url, emailpropertyTypeis also mandatory on every item (omitting it gives the same error for'undefined'/'null').Consequence: a list using
textarea,datetime,tel,select,multiselectorprioritycannot be edited non-destructively at all — every change to it has to go through the destructive DSL rebuild. That is a real, permanent constraint on #22's guard, not an edge case: the moment a user picks one of the six richer types in #18's builder, they lose safe editing on that list forever.Exposed as
ListFieldType.PropertiesEditableandListSchema.SupportsPropertiesEditso the UI can tell the user before they choose.2. "Destructive" means columns, not rows — so #22's confirmation copy must not say "rows will be deleted"
Confirmed live. The DSL rebuild:
- drops and recreates every column with a new id, losing any
validation/options/visibilitythe new DSL omits; - leaves rows intact, but values whose keys no longer exist are orphaned in
rowData— still stored, not shown, not validated.
And it renames the list:
schema.nameoverwrote the title ('ZZ Throwaway Schema Probe'→'ZZ Renamed By DSL Rebuild'), andschema.descriptionoverwrites the list description too — clearing it when omitted. #22's confirmation needs to say all three things.3. Deleting a data-bearing column requires
?force=true409 {…, "propertiesWithData": ["link"]}?force=truethen strips that key from every row. The sharedEnsureSuccessAsyncdiscards thepropertiesWithDataarray, so a dedicatedListSchemaExceptioncarriesIssues/PropertiesWithData/RequiresForce. #18 should name the affected columns in its confirmation rather than showing a bare 409.4. The server never validates conditional visibility — #20 must
An unknown operator, a missing watched key, and a forward reference all return
200and then silently never match. So there is no server-side safety net:ListSchema.Validate()does these checks client-side, and #20's editor should surface them rather than letting a rule quietly do nothing.5.
propertiesPUT definitively preserves row data — #22's guard is soundA rename + column-add on a list holding two rows returned
200and both rows came back with identical ids, identicalversion(still 1), and identicalrowData. Column ids were preserved too, and thevalidationRules/helpText/visibilityConditionthat thepropertiesshape cannot even express survived untouched.Note the envelope differs between the two directions:
GETreturns{"data":{…}}, thepropertiesPUTreturns{"properties":[…]}ordered bydisplayOrder— nodatawrapper.6. Schema-less lists are unaffected
Created without a schema they answer
{"data":{"name":"<list title>","description":…,"fields":[]}}and accept rows with arbitrary, even nested, keys. Unknown keys are accepted on a schema'd list too — nothing is stripped.fields: []cannot be PUT back (DSL must have at least one field), soListSchema.Validate()catches that locally.Also relevant to #21
Row-write validation returns
422withdetails:[{field,message}], which the current plumbing flattens — so #21 can't currently attach a message to the offending field without unpacking that itself.Separately, #144/PR #146 fixed a crash on the row-read path (
ListDataRow.ListIdwasrequiredbut absent) and modelledversion, which is the hook for optimistic concurrency on row edits.- drops and recreates every column with a new id, losing any
Scope
Expose each field's
validationobject and pre-validate rows before they hit the API.minnumber,date,datetime<label> must be at least <min>/... must be after <min>maxnumber,date,datetime<label> must be at most <max>/... must be before <max>minLengthtext,textarea<label> must be at least <n> charactersmaxLengthtext,textarea<label> must be at most <n> characterspatterntext,textarea,email<label> format is invalidstepnumberType-level checks always run regardless of
validation:emailmust be a valid address,urla valid URL,booleana real boolean,select/multiselectvalues must be withinoptions(<label> must be one of: ...), and a required empty value yields<label> is required.Rules are applied by the server on
POST/PUT /api/lists/{id}/data— not when the schema is created.Acceptance criteria
400still maps back to the right field rather than a generic toast.stepis applied to the number input only, and is not treated as a constraint.