| `__adjust` | `adjust_dialog.rs` | Image › Adjustments… (also sets `__filter` + `__preview` for live preview) |
| `__filter` | `filter_dialog.rs` (+ `select_mask_view.rs` when `__command` is `select.refineEdge`) | Filter Gallery commands, Edit › Color Settings…, Select and Mask, and other schema dialogs |
| Camera Raw | `camera_raw_ui.rs` | Separate OS window on desktop; see control-protocol § Camera Raw |
| Error message | `DialogKind::Error` | e.g. `move_lock`—message + OK |
| Nested pickers | `color_picker_ui` (+ markers in **Nested color pickers**) | Stack on parent; parent confirm refused while child open |
## Rules
### Architecture and commands
1.**Thin UI, engine command on OK.** Run `app.run` or a dedicated `*_ui::confirm`; one history step unless the command itself batches.
2.**Register in the hub.** Add `owns()` (or a stable marker), `body()`, `open()` if needed, and matching arms in `dialogs::show`, `confirm`, and `cancel` (and `open_command_dialog` when menu-driven).
3.**Extend existing paths first.** Filters → `filter_dialog`; adjustments → `adjust_dialog`; simple param grids → `__form`; avoid a second modal stack unless product needs Camera Raw–level UX.
4.**`fields` is the automation contract.** Anything agents must drive lives in named fields, not only in local egui state.
5.**Remember last-used values** after successful OK where Photoshop does (`filter_dialog::remember`, prefs `dialogs["…"]`, module-specific helpers).
### Chrome and layout
6.**Use `dialog_buttons`** for OK / Cancel / Apply—platform order via `ButtonRole` (macOS default action in the corner).
7.**Respect width rules** in `dialogs::show` unless `file_ui::dialog_width`, color picker, or kind-specific mins apply (typical 380–440; Layer Style / export / picker up to 600; New Document 800).
8.**Scroll long bodies** inside the content (`DIALOG_CHROME` for filters); title and button row stay visible.
9.**Transparent backdrop**—no dimming; previews are judged at true contrast.
10.**Position:** first open centered; then persist offset (`place_id` per command or kind); title bar drag; clamp on screen.
11.**Stacking:** only the **top** dialog is interactive; non-top instances render disabled; raise the live dialog if an older modal steals focus.
### Keyboard and focus
12.**Tab / ⇧Tab:** text and number fields only. Frame calls `field_tab::begin` / `end`; each field calls `field_tab::register` (`number_edit` does this; custom `TextEdit` must register manually).
13.**Do not rely on egui default Tab** through sliders, dropdowns, checkboxes, or preset cards.
14.**Open on first value:**`first_field_focused` + `widgets::focus_first_field` for value-first dialogs (most filters; Image/Canvas Size; not Select and Mask view keys).
15.**Select-all on focus** when reached via Tab or field arrows (`field_tab::select_all`; see `value_field`, `new_doc_ui`).
16.**Enter** confirms when OK is enabled (`can_ok`); exceptions must be explicit (`guide_layout_ui`, color picker).
17.**Esc** on topmost dialog with no popup open → Cancel. **Click outside does not dismiss** (canvas may still pan/zoom via `free_pointer_over`, `pan_delta`, `free_press`). Esc ignores Alt (it does not become Reset).
18.**Alt** turns Cancel into **Reset** when the dialog opts in via `dialog_alt_reset::supported` (dialog stays open). Opt-in markers: `__guideLayout`, `__fill`, `__variables`, `__colorPicker`, `__sizing`, `__adjust` (all kinds), `__filter`, `__form`. Each module implements its own `reset`; set `"__altReset": false` to disable.
### Canvas and preview
19.**Live preview** for filters/adjustments uses `filter_preview` / `adjust_preview`; `cancel()` clears preview and related shell state.
20.**Under-dialog canvas** respects dialog rects and open popups; Hand / Space / middle-button pan on free canvas only.
### Labels and i18n
21.**Titles:** command dialogs use `__label` (catalogued; trailing `…` stripped in chrome); fixed kinds use `dialogs::title` + `tl!`.
22.**User-visible strings:**`tl!("…")`; command labels from the menu catalog where possible.
### Confirm / cancel safety
23.**Child dialogs first**—refuse parent confirm if a nested color picker is open; cancel parent closes children where implemented.
24.**Validate before close** when params can be inconsistent (`guide_layout_ui::validate` in `confirm`).
25.**Disable OK** when required input is missing (`crop_presets::can_confirm`, Preferences Apply when unchanged).
26.**Preferences over control:** whole-dialog OK/Apply may be refused; safe single prefs via `prefs.set` (see control-protocol).
### Testing
27.**egui_kittest:** open dialog, `run_steps`, key presses, assert `fields` or accessibility roles (`field_tab` tests are the reference harness).
28.**Tab-order regressions:** add tests when a dialog gains multiple text/number fields.
29.**Filter preview:**`dialogs.rs` tests for preview persistence across background jobs.
### Anti-patterns
30. No panics in `body` / `confirm` on bad input—errors to status or validation.
31. No hard-coded colours or radii—`theme::Tokens` and `widgets.rs`.
32. Do not skip `confirm` dispatch—Enter and `ui.dialog.confirm` use it.
33. Do not expect click-outside-to-close on `egui::Modal`.
34. Do not register sliders or dropdowns for `field_tab` unless product explicitly changes Photoshop parity.
## Adding a command dialog (checklist)
1. Choose path: see **Command routing** (`adjust_dialog`, `filter_dialog`, `__form`, `__sizing`, …, or new `*_ui` with a unique marker).
3.`body(app, ui, &mut fields)`—mutate `fields` only; use shared widgets.
4.`confirm` / `cancel` arms in `dialogs.rs` (or delegate to module `confirm`).
5. Tab: register every text/number field; use `value_field` / `number_edit` where possible.
6.**Alt+Reset (optional):** implement `reset` on the module and add an arm in `dialog_alt_reset` (same dispatch order as `dialogs::show`). Snapshot open values under a `__*Orig` key when Reset should restore “as opened” rather than factory defaults.
7. Tests: at least open + confirm or Tab sequence if non-trivial; if Alt+Reset is supported, assert Reset restores fields and leaves the dialog open.
8. If menu-visible: command already in engine; menu id opens dialog via existing menu wiring.
## Known gaps
- **Tab registration** for plain `TextEdit` is manual; generic `__form` string fields may not join `field_tab` unless wired.
- **“Dialog module”** is any UI routed from `dialogs::show`, not only files named `*dialog*`.
- **Numeric field parity** (units in expressions, scrubby labels) is tracked in [`ui-parity.md`](ui-parity.md) (Navigation / numeric fields), not duplicated here.
## Revision history
| Date | Change |
|---|---|
| 2026-10-11 | Initial doc from dialog codebase audit |
| 2026-10-11 | Document all `DialogKind` values and `Command` routing markers |