Generalize useAutoSave to side-panel edit forms
Context
useAutoSave (app/composables/useAutoSave.ts) was introduced in the terminal-payment-support
branch as a generic debounced-patch composable (debounce + merge concurrent field edits + isSaving
flag). It currently has two consumers:
app/pages/admin/sales/payment-terminals.vue (description/icon inline fields)
app/components/Inventory/InventoryStockControl.vue (stock quantity stepper)
Both are single-field or few-field inline controls. Most of our admin CRUD pages instead use a
different, older pattern: a side-panel UForm with an explicit type="submit" "Enregistrer"
button (and often a separate "Supprimer" button), e.g.:
app/pages/admin/inventories/categories/index.vue
app/pages/admin/loans/recording-types/index.vue
app/pages/admin/loans/categories/index.vue
app/pages/admin/sales/payment-modes.vue
app/pages/admin/config/activities.vue
app/pages/admin/config/permissions.vue
These forms all follow the same shape: select a row → side panel opens with a UForm → edit fields
→ click "Enregistrer" to PATCH/POST → optionally "Supprimer" to delete. That's a lot of duplicated
boilerplate (loading flags, toast wiring, post-vs-patch branching, refresh-the-list-after) for the
same underlying pattern.
Goal
Explore replacing the explicit-submit pattern on these side-panel forms with the same debounced
auto-save UX already used for inline fields — so editing a category/recording-type/payment-mode/etc.
feels the same as editing a stock quantity: type, it saves, no button to remember to click.
Why
- Consistency: two different save UX patterns exist side by side (auto-save vs. explicit submit)
for what is conceptually the same "edit a record" interaction. Users shouldn't have to remember
which pages autosave and which don't.
- Less boilerplate:
updateCategory/updateRecordingType/etc. all hand-roll the same
loading/toast/create-vs-update branching that useAutoSave already centralizes.
- This came up because migrating
InventoryStockControl.vue to useAutoSave (terminal-payment-support
branch) made the contrast with the still-manual category form obvious — same underlying "PATCH one
field" need, two different UX patterns in the app.
What's missing from useAutoSave today
The composable currently assumes a single always-present patch target. To cover side-panel forms
it likely needs:
- Create support — side panels create a new record (no
uuid yet) before any field can be
saved. useAutoSave would need a "create on first field, then patch subsequent fields" mode, or
the caller has to eagerly POST an empty record on panel-open (behavior change to weigh).
- Delete action support — a
remove(item) alongside save, so the composable can also own the
loading/toast/refresh wiring for the "Supprimer" button, not just PATCH.
- Custom actions — some panels have extra buttons beyond save/delete (e.g. "Voir les articles"
nav link in categories, "move up/down" elsewhere) — these should stay outside the composable's
concern, but the API should not get in their way.
- Validation gate — several forms validate before submit (
UForm's :validate). Auto-save
needs a way to skip/hold the save while the form is invalid, rather than PATCHing bad data on
every keystroke.
- Per-field vs. whole-object save — inline usages call
save(item, {field: value}) per field;
side-panel forms currently submit the whole object at once. Need to decide whether auto-save
fires per-field-change (more granular, more requests) or debounces the whole form as one payload.
Suggested approach
- Prototype on one low-risk page first (
inventories/categories is a good candidate: simple
two-field form, already reviewed in this conversation) before rolling out further.
- Extend
useAutoSave (or introduce a sibling useAutoSaveForm) with remove() and validation-gate
support rather than bolting create/delete onto the existing single-purpose composable — keep the
inline-field use case (payment-terminals, stock control) simple and untouched.
- Once proven, migrate the other side-panel forms listed above one PR at a time.
Generalize
useAutoSaveto side-panel edit formsContext
useAutoSave(app/composables/useAutoSave.ts) was introduced in the terminal-payment-supportbranch as a generic debounced-patch composable (debounce + merge concurrent field edits +
isSavingflag). It currently has two consumers:
app/pages/admin/sales/payment-terminals.vue(description/icon inline fields)app/components/Inventory/InventoryStockControl.vue(stock quantity stepper)Both are single-field or few-field inline controls. Most of our admin CRUD pages instead use a
different, older pattern: a side-panel
UFormwith an explicittype="submit""Enregistrer"button (and often a separate "Supprimer" button), e.g.:
app/pages/admin/inventories/categories/index.vueapp/pages/admin/loans/recording-types/index.vueapp/pages/admin/loans/categories/index.vueapp/pages/admin/sales/payment-modes.vueapp/pages/admin/config/activities.vueapp/pages/admin/config/permissions.vueThese forms all follow the same shape: select a row → side panel opens with a
UForm→ edit fields→ click "Enregistrer" to PATCH/POST → optionally "Supprimer" to delete. That's a lot of duplicated
boilerplate (loading flags, toast wiring, post-vs-patch branching, refresh-the-list-after) for the
same underlying pattern.
Goal
Explore replacing the explicit-submit pattern on these side-panel forms with the same debounced
auto-save UX already used for inline fields — so editing a category/recording-type/payment-mode/etc.
feels the same as editing a stock quantity: type, it saves, no button to remember to click.
Why
for what is conceptually the same "edit a record" interaction. Users shouldn't have to remember
which pages autosave and which don't.
updateCategory/updateRecordingType/etc. all hand-roll the sameloading/toast/create-vs-update branching that
useAutoSavealready centralizes.InventoryStockControl.vuetouseAutoSave(terminal-payment-supportbranch) made the contrast with the still-manual category form obvious — same underlying "PATCH one
field" need, two different UX patterns in the app.
What's missing from
useAutoSavetodayThe composable currently assumes a single always-present
patchtarget. To cover side-panel formsit likely needs:
uuidyet) before any field can besaved.
useAutoSavewould need a "create on first field, then patch subsequent fields" mode, orthe caller has to eagerly
POSTan empty record on panel-open (behavior change to weigh).remove(item)alongsidesave, so the composable can also own theloading/toast/refresh wiring for the "Supprimer" button, not just PATCH.
nav link in categories, "move up/down" elsewhere) — these should stay outside the composable's
concern, but the API should not get in their way.
UForm's:validate). Auto-saveneeds a way to skip/hold the save while the form is invalid, rather than PATCHing bad data on
every keystroke.
save(item, {field: value})per field;side-panel forms currently submit the whole object at once. Need to decide whether auto-save
fires per-field-change (more granular, more requests) or debounces the whole form as one payload.
Suggested approach
inventories/categoriesis a good candidate: simpletwo-field form, already reviewed in this conversation) before rolling out further.
useAutoSave(or introduce a siblinguseAutoSaveForm) withremove()and validation-gatesupport rather than bolting create/delete onto the existing single-purpose composable — keep the
inline-field use case (payment-terminals, stock control) simple and untouched.