Adds SoftDelete/Trash across characters, chapters, locations, beats with a purge schedule and Trash page. Reworks the web client for keyboard-driven navigation (focus helpers, help overlay, keyboard.md doc). Moves the ChapterPage tag editor to the bottom of the page to match CharacterDetailPage.
61 lines
3.1 KiB
Markdown
61 lines
3.1 KiB
Markdown
# 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 `<textarea>` needs plain Enter to insert a newline, so multiline fields commit on
|
|
`mod+Enter` (Cmd or Ctrl) instead. The same combo, handled at the row/form level, means "I'm
|
|
done with this record" — closing a beat row, submitting a question. This mirrors the app's
|
|
original convention in `AgentPanel.tsx` (`mod+Enter` sends a message).
|
|
|
|
## Bare single letters create the primary thing on the page
|
|
|
|
`n` is the default create-hotkey across the app (new character, new chapter, new location). A
|
|
page with a second creatable thing uses a mnemonic instead (`b` for beat, `q` for question, `a`
|
|
for arc stage). Register these with `useHotkey` from the component that owns the create action,
|
|
so the shortcut is scoped to that page/section and unregisters when it unmounts — never
|
|
register a bare letter globally.
|
|
|
|
## Creating something puts focus in its first editable field
|
|
|
|
A create action that leaves the user hunting for the thing they just made is a bug. Land focus
|
|
in the new item's first field (or, when a mutation's response id isn't the field's DOM node
|
|
yet, request focus for that id and let it land once the row/page actually renders — see the
|
|
`focusRequestId`/`onAutoFocused` pattern used for beats and arc stages).
|
|
|
|
## Chip inputs commit on Enter, comma, or blur
|
|
|
|
`TagEditor`, `LocationEditor`, `CharacterMultiSelect`, and `AliasEditor` all add their draft
|
|
value to the list on Enter, comma, or losing focus. Follow the same shape for any new
|
|
chip-style input.
|
|
|
|
## Destructive confirmations use `ConfirmModal`
|
|
|
|
Never use the native `confirm()`/`alert()` dialogs — they're not stylable, not consistent with
|
|
the rest of the app, and (depending on browser) can be genuinely awkward to dismiss from the
|
|
keyboard. Use `ConfirmModal` (`src/components/ConfirmModal.tsx`), which wraps `Modal` and gets
|
|
focus-trapping and Escape-to-close for free.
|
|
|
|
## The exception, not the rule: `allowInInputs`
|
|
|
|
`useHotkey` shortcuts don't fire while a text field is focused, unless registered with
|
|
`allowInInputs: true`. Reserve that for shortcuts that make sense mid-typing (`mod+Enter` to
|
|
submit, `Escape` to close) — never a bare letter.
|