Add soft delete + trash, keyboard-first web overhaul, move chapter tags to bottom
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.
This commit is contained in:
@@ -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 `<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.
|
||||
Reference in New Issue
Block a user