+A question before an action is `ConfirmDialog` (`@sim/emcn/modal/confirm-dialog`), never a hand-built `Modal variant='confirm'` (`confirm-owner`): it holds the pending label (`Deleting…`), closes only on success and shows a failure. Pass `safe` for a confirm that loses nothing. Its title is the verb and the object in sentence case with no question mark (Delete link, Remove connection, Unsaved changes), the same for one object on every surface; Unsaved changes confirms with Discard. Editing saves with Save, creating says Create {noun}, adding a connection or credential says Add {noun}, and OAuth keeps Connect. Renames use the shared `RenameDialog`. Interface copy is sentence case: a literal `label`, `title`, `confirmLabel`, `pendingLabel` or `cancelLabel`, a `label` in menu, column, sort or command data, and a button, menu item or chip's text (`copy-case`). Product names (Sim, Chat, Assistant, Search), plans, the Agent block, brands and key names keep their capitals; they are listed, with multi-word names such as Slack Search and Compute Units, in `PROPER_NOUNS` and `PROPER_PHRASES` in `scripts/platform-source-rules.ts`. A page or tab name inside a label is a common noun (Open logs, Switch to runs). `copy-ellipsis` also reads an object or record's string values, so a status or column label writes `…` too. Statuses use Logs' vocabulary: a failed run reads Failed. A page's way back is `DirectoryBack`, and a Settings sub-view's is `DetailHeader`, each labelled with a bare noun (Skills, not Back to Skills); a hand-built text button led by the back glyph is reported (`back-owner`). Tabs are `ContentTabs` (`tab-owner`), and a message announced as an alert is `InlineNotice` with `intent='danger'` for a failed action, `FailureNotice` with Retry for a failed read, or the field's `error` (`alert-owner`): no hand-written `role='tab'`, `'tablist'` or `'alert'`. A full value on hover is `Tooltip` or `OverflowText`, never a native `title=` on a DOM element (`native-title`); an iframe's `title` is its accessible name and stays. An empty list says so with `EmptyState` (or `WorkspaceEmpty` in a workspace tab), and a narrow rail or a dialog's picker with `Muted`; a search that matches nothing reads No matching {noun}, with the query in curly quotes, and no period. Within one control bar, an action a viewer cannot take is hidden, or disabled with a real `disabledReason`. A justified exception is named, with its reason, in `CONFIRM_OWNERS`, `BACK_OWNERS`, `TAB_OWNERS` or `ALERT_OWNERS`.
0 commit comments