Lists: guard the destructive DSL rebuild vs the non-destructive properties edit - #164
Merged
Merged
Conversation
…edit
PUT /api/lists/{id}/schema is one route with two bodies whose consequences are
nothing alike, so the column editor now offers two clearly different actions
instead of one "Save schema" that would silently pick the destructive one:
* **Save columns** → `{ properties: [ … ] }`. Renames, adds, single removals and
reordering, applied in place: column ids kept, row data kept, and the list's
own title and description untouched. It also prints what it is about to do
("Save columns adds 1, renames 1, deletes link, reorders the columns — in
place, with every row's data kept") before it runs.
* **Rebuild columns…** → `{ schema: { … } }`. A separate, explicitly-labelled
action behind an in-place confirmation (no native dialog, nothing blocking the
dispatcher) that names exactly what is lost:
- every column dropped and recreated with a new id;
- rows are **NOT deleted** — values whose key no longer has a column stay in
each row, unshown and unvalidated, and the affected keys are named;
- validation rules and visibility conditions survive only because the drafts
re-send what they read, which the copy says plainly;
- the list's **title** is overwritten, from an editable box pre-filled with the
current one;
- the list's **description** is overwritten, and CLEARED if the box is blank.
Verified live today on throwaway lists (created, exercised, deleted — the account
is back to its one real list), driving the bodies the real draft view-model
serializes:
* Safe path with a rename + an added column + a reorder + dropping a data-bearing
column: 409 `propertiesWithData: ["link"]`, then `?force=true` → 200. Both rows
came back with identical ids, the dropped key stripped from each, and the
list's title and description untouched.
* Destructive path on the same list: 200, title became the name in the DSL, the
description survived when sent and came back null when omitted, and both rows
were still there with their ids.
That last asymmetry is why the two confirmations are worded differently, and it
is easy to get backwards: removing a column through `properties ?force=true`
DELETES that value from every row, while a rebuild KEEPS it as an orphan.
Closes #22
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 16, 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.
Stacked PR — 3 of 3
issue-22-destructive-guard→ baseissue-21-typed-row-editor(PR #163) →issue-18-column-builder(PR #161) →issue-17-list-schema-service(PR #143). Merge bottom-up: #143, #161, #163, then this.What this adds
PUT /api/lists/{id}/schemais one route with two bodies whose consequences are nothing alike, so the column editor offers two clearly different actions — never one “Save schema” that quietly picks the destructive one:Save columns →
{ properties: [ … ] }Renames, adds, single removals and reordering, applied in place: column ids kept, row data kept, the list's own title and description untouched. It states its plan first — “Save columns adds 1, renames 1, deletes link, reorders the columns — in place, with every row's data kept.”
Rebuild columns… →
{ schema: { … } }A separate, explicitly-labelled action behind an in-place confirmation (no native message box; nothing blocks the dispatcher) that names exactly what it costs:
When a draft can only go through the rebuild (any of the six richer types), the safe save refuses and points at the rebuild button by name instead of dead-ending.
Live evidence (probed today, 2026-09-16)
Driven with the bodies the real draft view-model serializes — a throwaway
net10.0console project<Compile Include>-ing the model and draft files by absolute path read the live list'sdata.properties[]and…/schema, built the drafts throughListColumnDraftViewModel.FromField, applied a rename + an add + a drop + a reorder, and emitted both bodies. Lists were titledZZ claude-probe …and deleted; a closingGET /api/listsshows only the account's pre-existingNew list.propertiesschemapropertiesWithData: ["link"]without?force=true; 200 with it)rowDataas orphansschema.namenullwhen omittedThat asymmetry in the middle row is the one that is easy to get backwards, and it is why the two confirmations are worded differently:
properties ?force=truedeletes the value, a rebuild keeps it as an orphan. The rebuild confirmation deliberately never says “rows will be deleted”.Verified in the same run: the safe save's 409 → confirm →
?force=truecycle; the title/description overwrite on rebuild; the description coming backnullfrom a rebuild that omits it; andListSchema.Validate()clean on the generated DSL.Left unverified
dotnet buildpasses in Debug and Release. The confirmation panel usesAmberBrush+Surface3Brushwith 3px corners on the 4pt grid, but it wants eyes on Windows — along with theComboBoxandDatePickernoted in Lists: schema column form builder (12 field types) #161/Lists: typed, schema-driven row editor (replaces the freeform JSON box) #163.Closes #22
🤖 Generated with Claude Code