Cars: drag the Information rows into the order you want

The rows on a car's Information tab now take a drag: they reorder as the
pointer crosses them and the arrangement saves on drop — or on dragend,
since a row released in the gap between rows never produces a drop and
would otherwise revert on the next load. Same native drag events as the
garage, so also pointer-only, and it needs write access.

The order belongs to the car, like the choice of which rows show at all,
so everyone it is shared with sees the same page. It is stored as the
full list of the 14 keys, hidden rows included: a row switched off and
back on returns to where it was rather than to the end. A key the stored
arrangement doesn't mention — a row added in a later release — follows
the arranged ones, the same rule the garage uses for a car added since
the last drag.

fieldOrder rides on the existing PUT /api/cars/{id}/view, which writes
only the lists it is given, so a drag never has to resend what is hidden.
A partial arrangement is accepted; an invented key is still a 400, which
is why normalizeHidden is now normalizeKeys — it validates an order as
well as a switched-off set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-08-17 20:52:21 +02:00
co-authored by Claude Opus 5
parent 049da69c83
commit bc798dae49
12 changed files with 210 additions and 47 deletions
+4
View File
@@ -279,6 +279,10 @@ const DESIRED = {
// default. Keys are validated in internal/api/cars.go.
F.json("hidden_tabs", 2000),
F.json("hidden_fields", 2000),
// The order the Information rows are laid out in, as field keys — the
// hidden ones included, so a row switched back on returns to where it was.
// Empty means the page's own default order.
F.json("field_order", 2000),
// Owner of this car. Non-cascading on purpose: deleting a user must not
// wipe their cars (account deletion in me.go intentionally leaves cars).
// required:false at the DB level — the API always sets owner on create and