Skip to content

feat(shortcuts): add Slack-style hotkeys - #2170

Merged
kalvinnchau merged 9 commits into
block:mainfrom
jwondrusch:jwondrusch/slack-hotkeys
Nov 3, 2026
Merged

kalvinnchau merged 9 commits into
block:mainfrom
jwondrusch:jwondrusch/slack-hotkeys

Conversation

@jwondrusch

@jwondrusch jwondrusch commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Adds Slack-style hotkeys to Buzz desktop, mostly from requests in #buzz-feedback.

Keys

Inbox and unread

  • ⌘⇧A opens Inbox with Unread only.
  • ⌘⇧T, ⌘⇧M and ⌘⇧K open Inbox filtered to Threads, Mentions and DMs.
  • Esc marks the open conversation read.
  • ⇧Esc asks for confirmation, then marks the whole community read.

Navigation

  • ⌥↑/↓ step through conversations in sidebar order. ⌥⇧↑/↓ step through unread ones only.
  • ⌘T opens Search Buzz, as a second default chord for ⌘K.
  • ⌘1–⌘9 jump to entries in the community switcher. ⌘1 is Personal space. ⌘⇧] and ⌘⇧[ move to the next and previous community, wrapping around.
  • ⌘⇧Y opens Set a status.
  • ⌘/ opens Settings → Shortcuts, using main's binding from feat(shortcuts): open Shortcuts settings with Mod+/ #2022. An earlier version of this branch had its own read-only ⌘/ panel. It was dropped so there's one place to see and change shortcuts.
  • Home moves to the placeholder ⌘⇧O so ⌘⇧A can open Inbox. This is to revisit before merge.

Messages

  • ↑ in an empty composer selects the newest message. ⌘↑ keeps the old edit-last behavior.
  • With a message selected:
    • ↑/↓ move the selection and Esc returns to the composer.
    • E edits and R reacts.
    • T or → replies in thread, and U marks unread.
    • Delete or Backspace deletes your own message, with the existing confirmation.
  • ⌘⇧\ reacts to the latest message.

Settings → Shortcuts

  • A shortcut can have several default chords, and Settings lists each one ("⌘K or ⌘T"). Two chords for the same physical key on a layout show once.
  • The message editor's formatting keys appear in a read-only "Message formatting" section. A shortcut rebound onto one of them shows a warning.
  • Slack's ⌘⇧C (code), ⌘⇧9 (quote), ⌘⌥⇧C (code block) and ⌘⇧U (link) are added as aliases of Buzz's own chords.

Fix: plain-Escape dismissals (thread, panels, drawer, plugin details) now ignore Shift, so ⇧Esc in a thread composer no longer closes the thread.

No FOUNDATION files are changed. Remind me (M) is left out because plugin message actions can't declare a key without a contract change.

Overlap with #2155

#2155 also adds ⇧Esc (without a confirmation) and the same Shift guards, and it edits ShortcutSettings.tsx. Whichever PR merges second will need a rebase.

Open questions

  • Should ↑ select messages (this PR), or keep Slack's ↑-to-edit, which nbl's ↑ → ⌘A → Delete flow relies on?
  • Home needs a permanent chord in place of ⌘⇧O.

Validation

  • Rebased onto main at 24c0adb.

  • pnpm check passes on 1b22c8f. Full vitest run on 1b22c8f: 15,523 passed and 1 failed. The failure is browser-host/media-preparation.integration.test.mjs (tiled HEIC), which this branch doesn't touch.

  • New tests:

    • message selection
    • the selected-message keys through the real dispatcher
    • community switching
    • ⌘T
    • the formatting aliases
    • the Settings read-only section
    • ⇧Esc leaving a tab-hosted side thread and an untabbed side panel open. The ⇧Esc-in-thread test fails without the Shift guard.
  • Browser journeys: none added or removed. Three were updated for intended behavior changes. On 1b22c8f, shortcuts, settings, settings-layout and nested-replies pass locally on chromium and webkit (68 tests). message-edit passed earlier on chromium.

    • nested-replies: edits with Up then E, because plain Up now selects the newest message.
    • message-edit: the composer fixture has no app shortcuts, so E isn't wired there. It now edits with ⌘↑, which still edits directly, and is renamed to match. Up → E is covered by src/app/message-shortcuts.test.ts.
    • shortcuts: the shortcut-counter example plugin moves from ⌘⇧K to ⌘⇧G, because ⌘⇧K now opens Inbox DMs as in Slack. Both are plugin keys, and buzz.inbox sorts before example.shortcut-counter, so Inbox's would win.
  • Test fixture: tests/fixtures/terminal-panel.tsx now caps its settings area's height. The new formatting group was pushing the terminal drawer below the webkit viewport. terminal-renderer and terminal-motion pass on webkit and chromium.

  • Agent exercise (1b22c8f): a throwaway Playwright check, on chromium and webkit, pressed ⌘/ in the composer. Settings → Shortcuts opened and listed the new rows (Set a status, Mark all as read, Message formatting). No separate shortcuts dialog appeared.

  • Human test: Jonathan tested Up-to-select, then E, R, T/→, U and Delete, plus ⌘1–9, ⌘⇧] / ⌘⇧[ and the Inbox keys, in the app. He re-checked ⌘/ on 1b22c8f, and it opens Settings → Shortcuts.

  • Plugin keys: Inbox's ⌘⇧A/T/M/K keep the existing plugin precedence. This is documented in docs/plugin-architecture.md and docs/buzz-help.md.

  • Performance: Settings builds the Shortcuts section the first time it's opened, not every time Settings opens. Settings renders all its sections in the background, and this branch roughly doubles the Shortcuts rows, so opening Settings had become about 20% slower in whole-app tests (found by comparing CPU profiles against main). After the change, the same tests run slightly faster than on main.

  • Known CI flakes not caused by this branch:

    • webkit nested-replies "crowded capped branches", which also fails on main
    • history-loading "held pointer scrolls" on webkit, which also fails on other PRs

buzz-review-completed

Screenshots

image image image image image image

🤖 Generated with Claude Code

@jwondrusch
jwondrusch marked this pull request as ready for review November 2, 2026 17:46
@jwondrusch
jwondrusch requested a review from a team as a code owner November 2, 2026 17:46
@jwondrusch
jwondrusch force-pushed the jwondrusch/slack-hotkeys branch 2 times, most recently from 41f2ded to 3d9b04a Compare November 2, 2026 18:24

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Two wrong-target shortcut defects were reproduced in Chromium and WebKit. Additional inline comments cover plugin compatibility and history traversal. Please resolve these before merging; native key delivery and final-head CI remain unverified.

Comment thread src/app/shortcuts.ts Resolved
Comment thread src/bundled/inbox/index.tsx Resolved
Comment thread src/bundled/inbox/InboxPage.tsx Resolved
@jwondrusch

Copy link
Copy Markdown
Contributor Author

🤖 @kalvinnchau Thanks for the review. All four findings are addressed, and CI is green on 2ec71bf. Could you take another look and approve if it holds up?

  • Newest reply by identity: Up and ⌘⇧\ now select the newest message by id, not the last row in tree order. Fixed in df8485d, with a test.
  • React-to-latest target: ⌘⇧\ selects the newest message first, even when an older one is selected. Fixed in df8485d, with a test.
  • Inbox on Back/Forward: returning to an Inbox shortcut visit no longer re-applies its filters. Fixed in df8485d, with a test.
  • Plugin precedence for ⌘⇧A/T/M/K: we're keeping the existing sorted-ID precedence and documenting it (a60a210). Settings flags shared chords with "Also used by". Any bundled-first ranking would come in its own PR.

Since your review:

  • 2ec71bf: Settings builds the Shortcuts section on first visit instead of on every open. The larger shortcut list had made whole-app tests about 20% slower.
  • 0ca5c44: the terminal test fixture caps its settings height so the drawer stays in view on webkit.

Jonathan has tested the keys by hand, and buzz-review-completed is in the description.

jwondrusch and others added 9 commits November 3, 2026 09:50
Inbox and unread: ⌘⇧A opens Inbox unread, ⌘⇧T/⌘⇧M/⌘⇧K open its
Threads/Mentions/DMs filters, Esc marks the open conversation read and
⇧Esc asks, then marks the community read.

Navigation: ⌥↑/↓ and ⌥⇧↑/↓ step conversations, ⌘T aliases Search (⌘K),
⌘1–⌘9 and ⌘⇧]/⌘⇧[ switch communities, ⌘⇧Y opens Set a status, ⌘/
opens a searchable read-only shortcuts panel.

Messages: Up in an empty composer selects the newest message (⌘↑ keeps
edit-last); with a message selected Up/Down move, Esc returns, E edits,
R reacts, T/→ replies in thread, U marks unread and Delete deletes your
own message; ⌘⇧\ reacts to the latest message.

Settings → Shortcuts lists every default chord of a shortcut and shows
the message editor's formatting keys read-only, with Slack's ⌘⇧C, ⌘⇧9,
⌘⌥⇧C and ⌘⇧U as formatting aliases. Plain-Escape dismissals ignore Shift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
Shift+Escape is Mark all as read, so the tab-hosted thread slot and the
untabbed side panel now ignore it, as PanelWorkspace and PanelCard already
do. Qualify ⌘↑ edit-last as macOS-only, fix the stale Archive comment, and
describe the keyboard shortcuts in the help guide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
Plain Up in an empty composer now selects the newest message, so the
nested-reply journey edits with Up then E, and the composer fixture
(which has no app shortcuts) edits with Cmd+Up. The shortcut-counter
example moves from ⌘⇧K, now Inbox DMs as in Slack, to the free ⌘⇧G.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
- Up selects the list owner's newest message by id, since a nested
  thread's tree order can render an older sibling last.
- ⌘⇧\ always reacts to the newest message in the active list, even while
  another message is selected; R reacts to the selection.
- Back/Forward to an Inbox shortcut visit keeps the filters chosen since
  instead of reapplying the route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
…s settings

The new Message formatting group makes the fixture's settings region tall
enough to push the terminal drawer below the WebKit viewport, where xterm
renders no rows. Bound the settings container so the drawer stays visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
Inbox's ⌘⇧A/T/M/K are ordinary plugin contributions and keep the existing
precedence; note the "Also used by" marker and rebinding as the resolution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
… ⌘/ list only while open

The two same-scope Settings destination tests mount the whole app with the
default 5s limit, which main already runs at 3.7-4.0s in CI; the new shell
shortcut components add about 20% and tip them over. Use the 15s timeout the
neighboring whole-app tests in this file already use.

The closed Keyboard shortcuts panel no longer subscribes to shortcut, binding
and plugin changes; its list mounts with the dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
…ngs open

Settings mounts every section hidden, so each open built the whole Shortcuts
list, one tooltip-bearing Change button per row. This branch roughly doubles
those rows, which made whole-app tests that open Settings about 20% slower
(CPU profile: Base UI tooltip mounts and shortcut rows). The section now
mounts the first time it is selected and stays mounted after, so its state
still survives section switches. The same-scope Profile tests run faster than
on main again, so the earlier timeout bump is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
Main now binds Mod+/ to open Settings → Shortcuts (block#2022). Drop this
branch's read-only ⌘/ panel so there is one place to see and change
shortcuts, and describe ⌘/ that way in the help and plugin docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
@jwondrusch
jwondrusch force-pushed the jwondrusch/slack-hotkeys branch from 2ec71bf to 1b22c8f Compare November 3, 2026 15:58
@jwondrusch
jwondrusch marked this pull request as draft November 3, 2026 15:59
@jwondrusch
jwondrusch marked this pull request as ready for review November 3, 2026 16:06
@jwondrusch

Copy link
Copy Markdown
Contributor Author

🤖 @kalvinnchau I rebased this onto main (24c0adbfe) at 1b22c8ff0. One behavior change since your approval: main's #2022 already binds ⌘/ to open Settings → Shortcuts, so I dropped this branch's separate read-only ⌘/ panel and kept main's binding. The rest is the same change you approved.

  • pnpm check passes. Full vitest fails only the known HEIC test.
  • The shortcuts, settings, settings-layout and nested-replies browser journeys pass on chromium and webkit.
  • Jonathan re-tested ⌘/ in the app.

Could you take another look and merge once CI is green? Jonathan's account can't merge.

@kalvinnchau
kalvinnchau merged commit 48bea39 into block:main Nov 3, 2026
42 checks passed
dr0uleau added a commit that referenced this pull request Nov 3, 2026
…xecution-support-for-native-buzz-33ea2bea

* origin/main: (74 commits)
  feat(host): harden byte lifecycle and add protected image upload (#2199)
  Updated onboarding (#2183)
  perf(relay): read each signed roster once in large channels (#2244)
  AIDA-2346: Keep spoilers hidden in reminder previews (#2261)
  fix: AIDA-2371 Keep sidebar selection pill aligned during resize (#2182)
  fix(relay): skip background rereads of profiles a read found absent (#2147)
  fix: prevent startup logo selection (#1823)
  fix(agents): send sign-in evidence with agent Git credentials (#2240)
  perf(mentions): stop scanning every member on each mention keystroke (#2246)
  fix(attachments): send image dimensions so previews don't shift (#2262)
  feat(shortcuts): add Slack-style hotkeys (#2170)
  perf(plugins): share host request clients across calls (#2243)
  fix(identity): keep federated sessions and agent grants in one keychain item (#2225)
  fix(profiles): evict the least recently requested profile (#2255)
  fix(unread): repair unread from the recovery tick only every 15 minutes (#2252)
  AIDA-2374: Use readable labels in chat titles (#2185)
  perf(profiles): keep verified names checked through a planned renewal (#2234)
  feat(plugins): let a plugin ask Buzz to review an install (#2059)
  fix(identity): sign FI builds in at first run through the native session (FOUNDATION: App.tsx, services.ts) (#2249)
  Place profile verification beside the verified name (#2224)
  ...

Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
dr0uleau added a commit that referenced this pull request Nov 3, 2026
* commit 'c8e882165': (50 commits)
  Scope execution targets by harness session
  Read durable execution state before admission
  feat(host): harden byte lifecycle and add protected image upload (#2199)
  Updated onboarding (#2183)
  perf(relay): read each signed roster once in large channels (#2244)
  AIDA-2346: Keep spoilers hidden in reminder previews (#2261)
  fix: AIDA-2371 Keep sidebar selection pill aligned during resize (#2182)
  fix(relay): skip background rereads of profiles a read found absent (#2147)
  fix: prevent startup logo selection (#1823)
  fix(agents): send sign-in evidence with agent Git credentials (#2240)
  perf(mentions): stop scanning every member on each mention keystroke (#2246)
  fix(attachments): send image dimensions so previews don't shift (#2262)
  feat(shortcuts): add Slack-style hotkeys (#2170)
  perf(plugins): share host request clients across calls (#2243)
  fix(identity): keep federated sessions and agent grants in one keychain item (#2225)
  fix(profiles): evict the least recently requested profile (#2255)
  fix(unread): repair unread from the recovery tick only every 15 minutes (#2252)
  AIDA-2374: Use readable labels in chat titles (#2185)
  perf(profiles): keep verified names checked through a planned renewal (#2234)
  feat(plugins): let a plugin ask Buzz to review an install (#2059)
  ...

Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
ljknight added a commit to ljknight/buzz-app that referenced this pull request Nov 3, 2026
…ection

* origin/main: (39 commits)
  Authorize local apps to use Buzz MCP tools (block#2211)
  feat(bestie): choose the private channels Bestie can read (block#2036)
  Polish message actions, spacing, and membership notices (block#2258)
  test(unread): prevent startup catch-up in receipt reserve journey (block#2274)
  test(search): wait for rejected join clearance to settle (block#2278)
  test(reactions): provide stable emoji media for icon rendering (block#2279)
  feat(profiles): reserve profile room for an open channel's roster (block#2269)
  feat(host): harden byte lifecycle and add protected image upload (block#2199)
  Updated onboarding (block#2183)
  perf(relay): read each signed roster once in large channels (block#2244)
  AIDA-2346: Keep spoilers hidden in reminder previews (block#2261)
  fix: AIDA-2371 Keep sidebar selection pill aligned during resize (block#2182)
  fix(relay): skip background rereads of profiles a read found absent (block#2147)
  fix: prevent startup logo selection (block#1823)
  fix(agents): send sign-in evidence with agent Git credentials (block#2240)
  perf(mentions): stop scanning every member on each mention keystroke (block#2246)
  fix(attachments): send image dimensions so previews don't shift (block#2262)
  feat(shortcuts): add Slack-style hotkeys (block#2170)
  perf(plugins): share host request clients across calls (block#2243)
  fix(identity): keep federated sessions and agent grants in one keychain item (block#2225)
  ...

# Conflicts:
#	src/features/messages/ThreadPanel.tsx
matt2e added a commit that referenced this pull request Nov 4, 2026
Main's new `index.test.tsx` (#2170) activates the channels plugin with a
hand-built context that has no `adminAccess`, and this branch reads
`ctx.adminAccess.membership` in `apply()` for the starter-channel switch, so
all six shortcut cases threw a TypeError on CI (JavaScript 2/4). Provide
`{ membership: undefined }`, as `tests/fixtures/mentions.tsx` and
`conversation.tsx` already do; the host always injects it.

Fails 6/6 at the previous commit, passes 6/6 with this line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e added a commit that referenced this pull request Nov 4, 2026
Main's new `index.test.tsx` (#2170) activates the channels plugin with a
hand-built context that has no `adminAccess`, and this branch reads
`ctx.adminAccess.membership` in `apply()` for the starter-channel switch, so
all six shortcut cases threw a TypeError on CI (JavaScript 2/4). Provide
`{ membership: undefined }`, as `tests/fixtures/mentions.tsx` and
`conversation.tsx` already do; the host always injects it.

Fails 6/6 at the previous commit, passes 6/6 with this line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
arividar pushed a commit to minniai/minni-buzz-app that referenced this pull request Nov 4, 2026
Adds Slack-style hotkeys to Buzz desktop, mostly from requests in
#buzz-feedback.

## Keys

**Inbox and unread**
- ⌘⇧A opens Inbox with Unread only.
- ⌘⇧T, ⌘⇧M and ⌘⇧K open Inbox filtered to Threads, Mentions and DMs.
- Esc marks the open conversation read.
- ⇧Esc asks for confirmation, then marks the whole community read.

**Navigation**
- ⌥↑/↓ step through conversations in sidebar order. ⌥⇧↑/↓ step through
unread ones only.
- ⌘T opens Search Buzz, as a second default chord for ⌘K.
- ⌘1–⌘9 jump to entries in the community switcher. ⌘1 is Personal space.
⌘⇧] and ⌘⇧[ move to the next and previous community, wrapping around.
- ⌘⇧Y opens Set a status.
- ⌘/ opens Settings → Shortcuts, using main's binding from block#2022. An
earlier version of this branch had its own read-only ⌘/ panel. It was
dropped so there's one place to see and change shortcuts.
- Home moves to the placeholder ⌘⇧O so ⌘⇧A can open Inbox. This is to
revisit before merge.

**Messages**
- ↑ in an empty composer selects the newest message. ⌘↑ keeps the old
edit-last behavior.
- With a message selected:
  - ↑/↓ move the selection and Esc returns to the composer.
  - E edits and R reacts.
  - T or → replies in thread, and U marks unread.
- Delete or Backspace deletes your own message, with the existing
confirmation.
- ⌘⇧\ reacts to the latest message.

**Settings → Shortcuts**
- A shortcut can have several default chords, and Settings lists each
one ("⌘K or ⌘T"). Two chords for the same physical key on a layout show
once.
- The message editor's formatting keys appear in a read-only "Message
formatting" section. A shortcut rebound onto one of them shows a
warning.
- Slack's ⌘⇧C (code), ⌘⇧9 (quote), ⌘⌥⇧C (code block) and ⌘⇧U (link) are
added as aliases of Buzz's own chords.

**Fix:** plain-Escape dismissals (thread, panels, drawer, plugin
details) now ignore Shift, so ⇧Esc in a thread composer no longer closes
the thread.

No FOUNDATION files are changed. Remind me (M) is left out because
plugin message actions can't declare a key without a contract change.

## Overlap with block#2155

block#2155 also adds ⇧Esc (without a confirmation) and the same Shift guards,
and it edits `ShortcutSettings.tsx`. Whichever PR merges second will
need a rebase.

## Open questions
- Should ↑ select messages (this PR), or keep Slack's ↑-to-edit, which
nbl's ↑ → ⌘A → Delete flow relies on?
- Home needs a permanent chord in place of ⌘⇧O.

## Validation
- Rebased onto main at 24c0adb.
- `pnpm check` passes on 1b22c8f. Full `vitest run` on 1b22c8f:
15,523 passed and 1 failed. The failure is
`browser-host/media-preparation.integration.test.mjs` (tiled HEIC),
which this branch doesn't touch.
- New tests:
  - message selection
  - the selected-message keys through the real dispatcher
  - community switching
  - ⌘T
  - the formatting aliases
  - the Settings read-only section
- ⇧Esc leaving a tab-hosted side thread and an untabbed side panel open.
The ⇧Esc-in-thread test fails without the Shift guard.
- Browser journeys: none added or removed. Three were updated for
intended behavior changes. On 1b22c8f, `shortcuts`, `settings`,
`settings-layout` and `nested-replies` pass locally on chromium and
webkit (68 tests). `message-edit` passed earlier on chromium.
- `nested-replies`: edits with Up then E, because plain Up now selects
the newest message.
- `message-edit`: the composer fixture has no app shortcuts, so E isn't
wired there. It now edits with ⌘↑, which still edits directly, and is
renamed to match. Up → E is covered by
`src/app/message-shortcuts.test.ts`.
- `shortcuts`: the `shortcut-counter` example plugin moves from ⌘⇧K to
⌘⇧G, because ⌘⇧K now opens Inbox DMs as in Slack. Both are plugin keys,
and `buzz.inbox` sorts before `example.shortcut-counter`, so Inbox's
would win.

- Test fixture: `tests/fixtures/terminal-panel.tsx` now caps its
settings area's height. The new formatting group was pushing the
terminal drawer below the webkit viewport. `terminal-renderer` and
`terminal-motion` pass on webkit and chromium.
- Agent exercise (1b22c8f): a throwaway Playwright check, on chromium
and webkit, pressed ⌘/ in the composer. Settings → Shortcuts opened and
listed the new rows (Set a status, Mark all as read, Message
formatting). No separate shortcuts dialog appeared.
- Human test: Jonathan tested Up-to-select, then E, R, T/→, U and
Delete, plus ⌘1–9, ⌘⇧] / ⌘⇧[ and the Inbox keys, in the app. He
re-checked ⌘/ on 1b22c8f, and it opens Settings → Shortcuts.
- Plugin keys: Inbox's ⌘⇧A/T/M/K keep the existing plugin precedence.
This is documented in `docs/plugin-architecture.md` and
`docs/buzz-help.md`.
- Performance: Settings builds the Shortcuts section the first time it's
opened, not every time Settings opens. Settings renders all its sections
in the background, and this branch roughly doubles the Shortcuts rows,
so opening Settings had become about 20% slower in whole-app tests
(found by comparing CPU profiles against main). After the change, the
same tests run slightly faster than on main.
- Known CI flakes not caused by this branch:
- webkit `nested-replies` "crowded capped branches", which also fails on
main
- `history-loading` "held pointer scrolls" on webkit, which also fails
on other PRs

buzz-review-completed

## Screenshots

<img width="1162" height="705" alt="image"
src="https://github.com/user-attachments/assets/f04e6d27-4768-4dc7-9061-ef98ce24ceb2"
/>
<img width="1160" height="777" alt="image"
src="https://github.com/user-attachments/assets/5091e3a0-cfd4-405e-b7a5-576d1b22040e"
/>
<img width="1168" height="780" alt="image"
src="https://github.com/user-attachments/assets/d8d7a056-ac41-47d0-86a1-1bc2f871994c"
/>
<img width="1160" height="777" alt="image"
src="https://github.com/user-attachments/assets/f7abaaa8-0e64-44a2-bc68-64135a56da69"
/>
<img width="1164" height="773" alt="image"
src="https://github.com/user-attachments/assets/b53a8177-2140-49dd-92c1-a64ace65c5a9"
/>
<img width="1162" height="774" alt="image"
src="https://github.com/user-attachments/assets/9489170d-8164-4d52-8b5d-b7ffa6597aad"
/>


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Jonathan Wondrusch <jwondrusch@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fizz <3585962401ebbd54cbcb1b18959ed64c2a9f769a7ce7dee259a85c05930427cc@sprout-oss.stage.blox.sqprod.co>
matt2e added a commit that referenced this pull request Nov 4, 2026
Main's new `index.test.tsx` (#2170) activates the channels plugin with a
hand-built context that has no `adminAccess`, and this branch reads
`ctx.adminAccess.membership` in `apply()` for the starter-channel switch, so
all six shortcut cases threw a TypeError on CI (JavaScript 2/4). Provide
`{ membership: undefined }`, as `tests/fixtures/mentions.tsx` and
`conversation.tsx` already do; the host always injects it.

Fails 6/6 at the previous commit, passes 6/6 with this line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e added a commit that referenced this pull request Nov 4, 2026
Main's new `index.test.tsx` (#2170) activates the channels plugin with a
hand-built context that has no `adminAccess`, and this branch reads
`ctx.adminAccess.membership` in `apply()` for the starter-channel switch, so
all six shortcut cases threw a TypeError on CI (JavaScript 2/4). Provide
`{ membership: undefined }`, as `tests/fixtures/mentions.tsx` and
`conversation.tsx` already do; the host always injects it.

Fails 6/6 at the previous commit, passes 6/6 with this line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e added a commit that referenced this pull request Nov 5, 2026
Main's new `index.test.tsx` (#2170) activates the channels plugin with a
hand-built context that has no `adminAccess`, and this branch reads
`ctx.adminAccess.membership` in `apply()` for the starter-channel switch, so
all six shortcut cases threw a TypeError on CI (JavaScript 2/4). Provide
`{ membership: undefined }`, as `tests/fixtures/mentions.tsx` and
`conversation.tsx` already do; the host always injects it.

Fails 6/6 at the previous commit, passes 6/6 with this line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
wesbillman pushed a commit that referenced this pull request Nov 5, 2026
…2402)

Scope: let ⇧Esc reach Mark all as read while focus is in the Channel
settings panel. Non-goals: other Escape handlers and any change to the
shortcut itself. Reuses the guard from the sibling panels. One file, 2
production lines.

## What was wrong

With focus inside a channel's settings panel, ⇧Esc (Mark all as read,
from #2170) closed the panel and marked nothing read.

## Why

On `main`, the panel's `onKeyDown` in
`src/bundled/channels/ChannelSettingsPanel.tsx:101` closes on any
`Escape` from inside the panel and calls `event.stopPropagation()`.
Since #2506 it skips events that are already handled or come from a
portalled layer, but it still has no `!event.shiftKey` check. The
sibling panels got that check in #2170
(`src/features/panels/PanelCard.tsx:32`,
`src/bundled/channels/useChannelPanels.tsx:152`). Because the panel
stops the event, the window shortcut dispatcher
(`src/features/shortcuts/service.ts`,
`src/app/shell/MarkAllReadShortcut.tsx`) never sees ⇧Esc.

The fix adds the same `!event.shiftKey` guard and comment the siblings
use, next to the #2506 checks. Plain Esc still closes the panel.

## How verified

- Extended the existing Escape test in
`src/bundled/channels/ChannelSettingsPanel.test.tsx`, "closes with
Escape or its close button without swallowing other keys": ⇧Esc does not
call `close` and reaches a `window` keydown listener; plain Esc still
closes and does not reach it. It fails without the fix and passes with
it.
- `bin/pnpm vitest run
src/bundled/channels/ChannelSettingsPanel.test.tsx`: 28 passed after
merging `main`, including #2506's handled and portalled Escape test.
- `bin/just iterate`: passed.

## In-app check

Build: this PR at `4b286248b`, with #2403, #2404 and #2405 at their
heads then, merged locally onto `main` at `cb8c971e9`. Run as the macOS
desktop app with `BUZZ_DEV_VIEWER= just desktop`. The branch was later
merged with `main` twice to resolve conflicts with #1624 (test file
only) and #2506 (the same Escape handler). The production behavior of
this fix did not change: ⇧Esc still passes through and plain Esc from a
focused control in the panel still closes it. #2506 only adds its own
skips for handled and portalled events.

With the mouse alone, focus never lands inside the panel in the WebKit
app, so these steps need macOS Keyboard navigation (System Settings >
Keyboard) or Tab focus.

1. Open Channel actions > View channel details. Press Tab until a
control in the panel shows a focus ring. Esc closes the panel.
2. Reopen the panel and Tab in again. ⇧Esc keeps the panel open and
shows "Mark everything as read?".

Where focus goes after opening the panel from the menu is a separate
issue.

buzz-review-completed

---------

Signed-off-by: Cheng Peng <cheng@block.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants