Skip to content

Schema: replace the freeform JSON row editor with a typed, schema-driven one #21

Closed

Description

@Adron

Problem

Views/ListsView.xaml edits rows as raw JSON text. That was the correct workaround while the DSL was unverified; with the schema readable it is now the main usability gap between the app and the web product.

Scope

Generate the row form from the list's schema:

Type Control
text single-line text box
textarea multi-line text box
number numeric input (honors step)
boolean True/False dropdown — plus Neither (null) when the field is optional
date date picker (YYYY-MM-DD)
datetime date + time picker (ISO)
email text box with email validation
url text box with URL validation
tel text box (+ pattern if set)
select single-select from options
multiselect multi-select from options
priority select over low/medium/high/urgent

Row data is keyed by each column's key. Respect defaultValue, placeholder, helpText, visible, displayOrder and the conditional-visibility rules.

Acceptance criteria

  • Add and edit rows through typed controls driven by the live schema.
  • defaultValue pre-fills new rows.
  • The grid renders each column by its type (checkbox for boolean, chips for multiselect, etc.), not as JSON.
  • A raw-JSON escape hatch remains available for schema-less lists (confirmed still supported).
  • Rows created by the app are readable in the web UI and vice versa.

Activity

  1. added
    parityWeb/API feature-parity work
    P0Must have for a credible parity claim
    on Sep 15, 2026
  2. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    Live-verified findings from the schema service (#17 / PR #143) — three change the design here

    ⚠️ 1. The non-destructive properties form accepts only SIX types, not twelve

    400 Unknown propertyType 'textarea'. Allowed: text, number, boolean, date, url, email
    

    propertyType is also mandatory on every item (omitting it gives the same error for 'undefined'/'null').

    Consequence: a list using textarea, datetime, tel, select, multiselect or priority cannot 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.PropertiesEditable and ListSchema.SupportsPropertiesEdit so 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 / visibility the 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.name overwrote the title ('ZZ Throwaway Schema Probe' → 'ZZ Renamed By DSL Rebuild'), and schema.description overwrites 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=true

    409 {…, "propertiesWithData": ["link"]}
    

    ?force=true then strips that key from every row. The shared EnsureSuccessAsync discards the propertiesWithData array, so a dedicated ListSchemaException carries Issues / 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 200 and 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. properties PUT definitively preserves row data — #22's guard is sound

    A rename + column-add on a list holding two rows returned 200 and both rows came back with identical ids, identical version (still 1), and identical rowData. Column ids were preserved too, and the validationRules / helpText / visibilityCondition that the properties shape cannot even express survived untouched.

    Note the envelope differs between the two directions: GET returns {"data":{…}}, the properties PUT returns {"properties":[…]} ordered by displayOrder — no data wrapper.

    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), so ListSchema.Validate() catches that locally.

    Also relevant to #21

    Row-write validation returns 422 with details:[{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.ListId was required but absent) and modelled version, which is the hook for optimistic concurrency on row edits.

  3. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    Implemented in #163 — work continues in the PR from here. Rows on a schema'd list are now edited through generated controls (tri-state boolean with Neither, multiselect checkboxes, date and date+time pickers, validated email/url, priority over its four values), keyed by each column's key, with defaults pre-filled and conditional visibility evaluated client-side since the server never applies it. The grid renders typed cells instead of JSON, and the raw-JSON hatch stays for schema-less lists.

    Two live findings drove the design: a row write REPLACES rowData (a one-key PUT dropped five other values), so the form re-sends every key it knows and echoes untouched values back verbatim, unknown keys included; and 422 carries details:[{field,message}] keyed by column key, which the shared error plumbing flattened — the row writes now throw ListRowValidationException so each message lands on its control.

    One dependency worth noting: GET /api/lists/{id}/data omits each row's listId, so reads go through a tolerant mapper until #146 relaxes ListDataRow. Also: the server ignores a version sent on a row write (a stale one still returned 200), so #146's Version is a display value, not a conflict check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Must have for a credible parity claimarea:listsArea: listsparityWeb/API feature-parity work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions