# Contract: Modal Workflows Version: 1.0 ## State Machine Every modal has exactly these states: ``` closed → open → submitting → success | error ↓ cancel → closed ``` - `open`: form visible, submit enabled. - `submitting`: form disabled, spinner on submit button, no double-submit possible. - `success`: close modal, show toast, refresh affected data. - `error`: stay open, show error message inline, re-enable form. - `cancel`: close without changes, no confirmation needed unless form is dirty. ## Rules - Destructive actions (delete, archive, bulk remove) require a separate confirmation modal — not just a disabled button. Confirmation modal must name the target: "Delete component ABC-123?" - Never close a modal automatically on error — keep it open so the user can retry or copy the error. - Submit button text must describe the action: "Save", "Delete", "Archive" — not "OK" or "Confirm". - Modal title must match the action: "Edit Component", "Delete Asset" — not generic "Modal". - Escape key and clicking the backdrop close the modal (unless in `submitting` state). ## Validation - Validate on submit server-side. Client-side validation is optional progressive enhancement only. - Show field-level errors inline below each field. - Show a form-level error summary at the top if multiple fields fail. - Error messages must be human-readable and action-oriented: "Serial number is required" — not "serial_number: cannot be null". ## htmx Pattern (server-rendered modals) ``` POST /api/entity → 200 OK + HX-Trigger: "entitySaved" (success) → 422 Unprocessable + partial HTML (validation error, re-render form) → 500 + error message (server error) ``` - On success: server sends `HX-Trigger` header, JS listener closes modal and refreshes list. - On validation error: server re-renders the form partial with inline errors (422). - On server error: show generic error toast, log full error server-side. - Do not use `200 OK` for validation errors — use `422` so htmx can differentiate. ## Multi-Step Modals Use only when the workflow genuinely requires staged input (e.g. import preview → confirm). - Show a step indicator (Step 1 of 3). - Back button must restore previous step values. - Final confirm step must summarise what will happen before the destructive/irreversible action. - Single-step edits must NOT be split into multi-step without good reason.