diff --git a/CLAUDE.md b/CLAUDE.md index 871fe82..02e8c67 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,7 @@ Serilog console via `AddSerilog` (not `UseSerilog` — keeps OTel provider for A - `PATCH` requests partial: null field = leave alone, empty string = clear. Keep new update endpoints consistent with `Patch.Apply`. - Enums cross wire as names, never ordinals - All frontend components should have an id attribute that identifies them uniquely. +- Web client is keyboard-first: read `docs/keyboard.md` before adding any interactive UI (forms, editable rows, create flows). ## Testing diff --git a/docs/keyboard.md b/docs/keyboard.md new file mode 100644 index 0000000..f038abb --- /dev/null +++ b/docs/keyboard.md @@ -0,0 +1,60 @@ +# Keyboard conventions + +Novelly's web client is built to be driven entirely from the keyboard. New interactive +components should follow these rules so the app stays consistent as it grows. + +## Escape cancels or closes — it never destroys already-saved work + +In an editor that commits per field (a beat row, a chapter's title), Escape reverts only the +field you're currently in and then closes the editor. Fields you already tabbed past and +committed stay saved — Escape is honest about this, not a full undo. Anywhere a component *can* +offer a true "discard everything" cancel (a create form that hasn't saved anything yet), do +that instead. + +## Enter commits a single-line field and advances + +Pressing Enter in a single-line field is equivalent to Tab: it commits the field's value and +moves focus to the next field. Shift+Enter moves to the previous field. This is what +`AutoField` (`src/components/ui.tsx`) does by default — reuse it rather than hand-rolling a +text input's key handling. + +## mod+Enter commits a multiline field or completes a record + +A `