This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit de96f3d
fix(app-shell,i18n): the keyboard-shortcuts dialog lists only shortcuts a mounted handler advertises (objectui#11674) (#11708)
Fixes #11674
Clause-②: no (narrowing)
The claim wrote `Clause-②: no`, with the rule that it becomes `no
(narrowing)` if published `console.shortcuts.*` locale keys retire.
Seven do, so the line reads `no (narrowing)`. No accept set widens, and
`@object-ui/app-shell` exports nothing new.
## What was wrong (measured on `origin/main` at `c3623eb`)
The dialog was a static list, kept apart from every key handler. Every
row, by the handler it had:
| Old row | Handler on `main` | Verdict |
|---|---|---|
| `⌘K` Open command palette | `CommandPaletteProvider` keydown (`k` with
⌘ or Ctrl) | wired |
| `?` Show keyboard shortcuts | the dialog's own keydown | wired |
| `Esc` Close dialog / panel | Radix dismiss, on every `Dialog` and
`Sheet` | wired |
| `B` Toggle sidebar | the `SidebarProvider` listener (synced Shadcn
primitive) answers `b` with ⌘ or Ctrl only. A bare `B` does nothing |
wrong chord |
| `⌘/` Focus search | none | dead |
| `N` Create new record | none. `useCrudShortcuts` /
`useKeyboardShortcuts` have no caller outside their module | dead |
| `R` Refresh data | none. The only `r` listener is the approvals
inbox's reject key, on that page only | dead |
| `⌘E` Edit selected record | none | dead |
| `⌘⇧O` New chat, `⌘⇧S` Toggle conversations list | `AiChatPage` only
(`matchAiChatShortcut`). The dialog mounts in `AppContent` (`/apps/...`)
and `/ai` is not under it, so neither handler is mounted where the rows
were shown | not wired where listed |
| `⌘D` Toggle dark mode | none | dead |
The card named five of these. The order asked for every row to be
measured, which added `R` and the AI pair.
## What changed
- **`packages/app-shell/src/chrome/advertisedShortcuts.ts` (new).** A
small module store, the same shape as `assistantBus`. The code that owns
a handler calls `useAdvertiseShortcut(descriptor, enabled?)` beside it,
so the shortcut is advertised while the handler is mounted.
`useAdvertisedShortcuts()` reads the current set. A descriptor is `{ id,
group, chord, label }`. The keycaps come from the chord
(`shortcutKeycaps`), and `label` is a literal `t('…')` call so the i18n
gates read the key (see the probe below).
- **`KeyboardShortcutsDialog`** has no list of its own any more. It
renders the advertised shortcuts, grouped (`general`, `navigation`,
`aiChat`) and sorted by their text, and each row carries
`data-shortcut-id`. It advertises its own `?`, and `Esc` (Radix's
dismiss) beside the `Dialog` that `Esc` closes.
- **`CommandPaletteProvider`** advertises `⌘K` beside its keydown
handler.
- **`ConsoleLayout`'s inner component**, which sits inside
`SidebarProvider`, advertises `⌘B`. The listener belongs to the
primitive. `packages/components/src/ui/sidebar.tsx` was read and not
edited.
- **`AiChatPage`** advertises `⌘⇧O` and `⌘⇧S` beside the listener that
calls `matchAiChatShortcut`. So they are listed only where that page is
mounted, and today the dialog is not mounted there.
- **`@object-ui/i18n`.** Seven keys were read only by the dead rows, and
they are removed from all ten packs: `console.shortcuts.focusSearch`,
`createRecord`, `refreshData`, `editRecord`, `toggleDarkMode`,
`groups.dataViews`, `groups.preferences`. Two i18n test comments cited
`toggleDarkMode` as an example of ё, and they are reworded. One of them
also carried a stale ё count, which is dropped, not re-counted.
- The app-shell README gains a "Keyboard-shortcuts dialog" section. The
changeset is `.changeset/11674-shortcuts-dialog-wired-only.md`, `minor`
for both packages, with the key removal spelled out as BREAKING at
`minor` under the repo's version policy.
Inside an app, the dialog now lists `⌘K`, `?`, `Esc` and `⌘B`.
Landing point: the order expected the static list in
`KeyboardShortcutsDialog.tsx`. That file is where the list was. The fix
also touches three files that register handlers, and only to advertise
them, as the claim's file surface allows.
## The pin
`packages/app-shell/src/chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx`:
- **Every listed row fires.** The test mounts the dialog where
`AppContent` mounts it: inside the real `ConsoleLayout`, with the real
`AppShell`, `SidebarProvider` and `CommandPaletteProvider`. It opens the
dialog with `?shortcuts=1`. For each row it fires the chord that the
row's own advertisement declares, once with ⌘ and once with Ctrl, each
on a fresh mount, and checks the real handler's effect: the palette
opens, the dialog opens, the dialog closes, the sidebar collapses. The
rows must equal the advertisements, keycaps included, and must equal the
probe table's keys. So a listed row that nothing can fire fails, and the
loop cannot pass by running nothing.
- **The list is derived.** A row is listed while its advertiser is
mounted and leaves when the advertiser unmounts.
- **The AI page's chords are the ones its real matcher answers.**
`matchAiChatShortcut` returns the right action for both chords.
- **Control.** A bare `b`, the old row, does not toggle the sidebar.
## Ablations (one-off, run from committed HEAD `05c1f48`, restore proved
by blob hash)
- **The defect's own shape.** The sidebar's advertisement was set back
to a bare `b` (`chord: { key: 'b' }`) with `ablation-replace` (anchor 1
→ 0, blob `de7e99c048be` → `93c37705f9e4`). The pin went red, `Tests 1
failed | 4 passed (5)`, at the sidebar probe: `data-sidebar-open` stayed
`"true"`. Restored: blob == HEAD `de7e99c048be`, `git diff HEAD` empty.
- **The old static dialog.** `KeyboardShortcutsDialog.tsx` was swapped
for its `c3623eb` bytes (`createRecord` marker 1, `useAdvertiseShortcut`
0, blob `ad882b0dc950`). The test went red, `Tests 2 failed | 3 passed
(5)`: the pin and the derivation case. Restored: blob == HEAD
`f766b2845931`.
- **The i18n gate reads the descriptor labels.**
`t('console.shortcuts.toggleSidebar')` in the sidebar descriptor was
renamed to a key no pack has. `check:i18n-keys` exited 1 with
`[missing-key] console.shortcuts.toggleSidebarProbe` at
`ConsoleLayout.tsx`. Restored: blob == HEAD.
## Gates (objectui's own; derived by hand from `package.json` and
`.github/workflows/`)
All at HEAD `05c1f48`. Each exit code was captured before any pipe.
| Gate | Exit | Reading |
|---|---|---|
| `pnpm exec vitest run --maxWorkers=2 packages/i18n/
packages/app-shell/src/chrome/ packages/app-shell/src/layout/
packages/app-shell/src/context/
packages/app-shell/src/console/__tests__/
packages/app-shell/src/console/ai/__tests__/` (under the verify lock) |
0 | `Test Files 193 passed (193)`, `Tests 2096 passed, 13 skipped` |
| `pnpm --workspace-concurrency=2 --filter "@object-ui/app-shell^..."
build` | 0 | dependency closure built |
| `pnpm --filter @object-ui/app-shell type-check` (both `tsc` legs) | 0
| `tsconfig.test.json --listFilesOnly` lists the new pin |
| `pnpm --filter @object-ui/i18n type-check` | 0 | |
| `eslint` over the 6 changed app-shell files, `--format json` | 0 | 0
errors. 38 warnings, against 36 on the 4 modified files at `c3623eb`
(read through `--stdin`). The +2 are
`react-refresh/only-export-components` on the two exported AI-page
descriptors |
| `eslint` over the 12 changed i18n files, `--format json` | 0 | 0
errors, 0 warnings |
| `check:i18n-keys` | 0 | every call-site key resolves (3273 keys) |
| `check:i18n-drift` | 0 | 0 `en` values changed; 7 keys removed |
| `check:i18n-dead-keys` (report-only) | 0 | 0 `console.shortcuts.*`
candidates; pack keys 3273 |
| `check:i18n-designer-parity`, `check:control-bytes`,
`check:new-line-citations` (0 new), `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`,
`check:unreferenced-sources`, `check:test-path-roots`,
`check:changeset-claims`, `check:pending-changeset-literals` | 0 each |
|
| `node scripts/check-changeset-no-major.mjs`,
`check-changeset-presence.mjs` (18 source files, 2 packages, 1
changeset), `check-changeset-fixed.mjs` | 0 each | |
| `check:readme-exports` | NOT MEASURED | PREREQUISITE: it needs every
package's `dist`. No export moved, and the new README section has no
fenced import |
| `check:eager-locale-catalogues` | NOT MEASURED | PREREQUISITE: it
needs `apps/console/dist/eager-closure.json` |
Why the lint narrowing holds:
- The population is each package's `lint` script, `eslint .`.
- `eslint.config.js` sets no `parserOptions.project` or
`projectService`, so no lint is type-aware.
- No rule under `eslint-rules/` reads another file.
So this diff cannot change the verdict on a file it did not touch. The
full `pnpm test`, repo-wide `pnpm lint` and the `apps/console` suites
are left to CI.
Declared narrowing: the verify lock was held for about 26 minutes by a
sibling's full-suite run. During that time the single pin file, and the
ablations, ran without the lock (`--maxWorkers=1`). The final runs in
the table above went through the lock.
## Acceptance notes (observations, not filed)
- **`⌘⇧I`, the chat-dock toggle in `ConsoleLayout`, is wired and not
advertised.** A row needs a new `console.shortcuts.*` key in ten packs.
That is a widening, outside this claim's `Clause-②: no`. With this
mechanism it costs one `useAdvertiseShortcut` call and the key. Carrier:
none.
- **Page-scoped shortcuts are wired and not advertised:** QuickFind
`⌘⇧M`, the resource editor's `⌘S` / `⌘\` / `Esc`, the approvals inbox's
list keys, the performance panel's `Ctrl+Shift+P`, and `useDebugMode`'s
`Ctrl+Shift+D`. Once advertised, each would be listed only on its own
page. This PR reads "lists exactly the shortcuts that are wired" as
"lists no shortcut that is not wired", plus the chrome layer that owned
the old list.
- **`useKeyboardShortcuts`, `useCrudShortcuts` and
`getShortcutDescriptions` in `@object-ui/react` are published and have
zero callers.** This is dead code, and wiring it is not this card's
question.
- **The keycaps draw `⌘` on every platform**, as before. Every
advertised handler also accepts Ctrl.
- **The dialog is not mounted on `/ai`**, and the `/ai` header hides the
Help-menu entry. So the AI pair is listed nowhere today. That is correct
under the rule, and it is unchanged.
- **Not measured, inferred from source:** the `SidebarProvider` listener
does not skip editable targets, so `⌘B` inside a rich-text field may
toggle the sidebar.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent eb4552e commit de96f3d
20 files changed
Lines changed: 600 additions & 128 deletions
File tree
- .changeset
- packages
- app-shell
- src
- chrome
- console/ai
- context
- layout
- i18n/src
- __tests__
- locales
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
974 | 974 | | |
975 | 975 | | |
976 | 976 | | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
977 | 990 | | |
978 | 991 | | |
979 | 992 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
5 | 10 | | |
6 | 11 | | |
7 | 12 | | |
8 | | - | |
| 13 | + | |
9 | 14 | | |
10 | 15 | | |
11 | 16 | | |
| |||
16 | 21 | | |
17 | 22 | | |
18 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
19 | 41 | | |
20 | | - | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
21 | 67 | | |
22 | 68 | | |
23 | 69 | | |
24 | 70 | | |
25 | | - | |
| 71 | + | |
| 72 | + | |
26 | 73 | | |
27 | | - | |
| 74 | + | |
28 | 75 | | |
29 | 76 | | |
30 | 77 | | |
31 | | - | |
| 78 | + | |
32 | 79 | | |
33 | 80 | | |
34 | 81 | | |
35 | 82 | | |
36 | 83 | | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
75 | 103 | | |
76 | 104 | | |
77 | 105 | | |
| |||
108 | 136 | | |
109 | 137 | | |
110 | 138 | | |
111 | | - | |
112 | | - | |
| 139 | + | |
| 140 | + | |
113 | 141 | | |
114 | | - | |
| 142 | + | |
115 | 143 | | |
116 | 144 | | |
117 | | - | |
| 145 | + | |
118 | 146 | | |
119 | | - | |
| 147 | + | |
| 148 | + | |
120 | 149 | | |
121 | 150 | | |
122 | | - | |
| 151 | + | |
123 | 152 | | |
124 | | - | |
| 153 | + | |
125 | 154 | | |
126 | 155 | | |
127 | 156 | | |
| |||
0 commit comments