Repository navigation
fix(channels): let shift+escape through the channel settings panel - #2402
Merged
wesbillman merged 4 commits intoNov 5, 2026
Merged
Conversation
The channel settings panel closed on any Escape and stopped propagation, so Shift+Escape (Mark all as read) never reached the window shortcut dispatcher. Guard it with !event.shiftKey like the sibling panels. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Cheng Peng <cheng@block.xyz> Co-authored-by: Cursor <cursoragent@cursor.com>
The new case mounted the same fixture as the existing Escape test and repeated its plain Escape assertion. Extend that test instead, as .agents/skills/test-audit asks for near-duplicates. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Cheng Peng <cheng@block.xyz>
…ettings-shift-esc * upstream/main: (91 commits) test(browser): click timeline rows only after the timeline settles (block#2560) ci: split Vitest across six shards so 3/4 stops hitting the job cap (block#2571) feat(github): unfurl PR and issue links inline with their title (block#1693) fix(auth): style federated sign-in callback pages (block#2549) fix(agents): align creation model controls with identity (block#2480) Run native Codex through plugin execution targets (block#2150) test: split Solo access browser scenarios (block#2499) feat(agents): let any agent change Buzz settings through the MCP (block#2248) Let plugins decorate host sidebar rows and add sort modes (block#1853) NEXUS-4082: Keep the new-message boundary stable during a visit (block#2083) feat(agents): move version history into settings (block#2374) fix(agents): read goose Activity by saved session ids and render memory bodies on open (block#2466) feat(channels): starter channels: admin switch and relay auto_join flag (block#1624) fix(plugins): read Square HTTPS sources with the Block SSH agent (block#2465) feat(plugins): let agents request plugin changes for owner review (block#2280) fix(messages): keep the hover toolbar while its menu is open (block#2439) feat(workflows): suggest channel members for @ in Send Message text (block#2309) AIDA-2344: Exclude muted channels from Dock (block#2313) feat(search): add slack-style message search operators on desktop (block#2463) test: fix invitation and coordination checks on main (block#2517) ... Signed-off-by: Cheng Peng <cheng@block.xyz> Co-authored-by: Cursor <cursoragent@cursor.com>
joahg
approved these changes
Nov 5, 2026
joahg
left a comment
Member
There was a problem hiding this comment.
🤖 Guard matches the sibling panels and the test covers both Shift+Esc and plain Esc. Merge state is DIRTY, needs a rebase.
…ettings-shift-esc * upstream/main: (40 commits) feat(conversation): let plugin rows manage messages without visiting a channel (block#2512) feat(github): make side-panel link opening optional (block#2629) feat(messages): show a blurred thumbnail until an image loads (block#2556) fix(relay): give large roster and read-state reads a 30s deadline (block#2627) fix(user-status): show a status emoji's :name: on hover where the full status shows (block#2384) fix(inbox): keep the list's place when returning in the same session (block#2134) fix(messages): pass verified labels to the forward recipient picker (block#2633) feat(messages): forward a message to a person (block#2167) docs(agents): design relay-facing paths for the whole fleet at once (block#2620) Route debug sessions through the credential helper (block#2541) perf(search): skip no-op slow-search resets on each keystroke (block#2605) perf(agents): dedupe channel members only for dm connection setup (block#2603) perf(search): keep the search tab row across palette keystrokes (block#2588) perf(sidebar): skip the hidden actions menu in solo and team headers (block#2582) perf(search): rank each palette name once per render (block#2606) perf(sidebar): skip re-rendering unchanged animated channel rows (block#2572) feat(settings): follow the system clock and let people hide message times (block#2440) feat(plugins): repository access rules for Git imports (block#2484) feat(desktop): add Settings to the macOS app menu (block#2594) Preserve and renew external app approval (block#2464) ... Signed-off-by: Cheng Peng <cheng@block.xyz> Co-authored-by: Cursor <cursoragent@cursor.com>
wesbillman
reviewed
Nov 5, 2026
wesbillman
left a comment
Collaborator
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes’s GitHub account.
No blockers. The ⇧Esc guard matches the sibling panels (PanelCard, PanelSubview, useChannelPanels). Plain Esc still closes and stops propagation, and the test covers both directions. ⇧Esc while typing in the settings editor now reaches the Mark all as read confirmation, which is consistent with that shortcut's allowInEditable. Independent of #2526, so they can merge in either order.
Validation: Court review of the source at this head plus green CI. No local runs.
wesbillman
approved these changes
Nov 5, 2026
wesbillman
pushed a commit
that referenced
this pull request
Nov 5, 2026
Scope: make the "newest message" lookup behind ↑ (select the newest message) and react to latest use the rendered message order. Non-goals: the same whole-second pattern in `ThreadPanel.tsx` and `unread.ts`, which touches the unread contract and needs its own PR. Reuses `compareMessages`; one file, 2 production lines. ## What was wrong When several messages land in the same second (agents do this a lot), ↑ in an empty composer and react to latest (⌘⇧\) select an earlier message instead of the newest one. ## Why `newest()` in `src/features/messages/use-message-selection.ts:29` compared `row.createdAt > newest.createdAt`. `createdAt` is whole seconds and the comparison is strict, so on a tie it kept the first row. The timeline renders rows by `compareMessages` (`src/features/relay/message-order.ts:17-24`: send ms, then event id), so the first row of a same-second burst is the oldest. Thread rows render in tree order, so taking the last row is not a fix there. The fix calls `compareMessages(row, newest) > 0` in the same reduce. `ChannelMessage` already carries `id`, `createdAt` and `createdAtMs`. No API change. ## How verified - New `src/features/messages/use-message-selection.test.tsx` mounts the hook with React Testing Library in StrictMode and calls `selectNewestMessage`. It covers a same-second burst ordered by send ms, a same-second tie broken by id, and an ordinary cross-second case that skips membership notices. - The same-second test fails on `main` (`Expected: "a"`, `Received: "z"`) and passes with the fix. - `pnpm vitest run src/features/messages/use-message-selection.test.tsx src/features/messages/message-selection.test.ts src/app/message-shortcuts.test.ts`: 14 passed. - `just iterate` (format, lint, typecheck, frontend build): passed. ## In-app check - Build: a local merge of the heads of #2402, #2403, #2404 and #2405 on `main` at `cb8c971e9`. macOS desktop, started with `BUZZ_DEV_VIEWER= just desktop`. - In a Solo chat, an agent sent 1, 2 and 3. With an empty composer, ↑ selected 3, and ⌘⇧\ opened the reaction picker on 3. - The same check on 0.7.8 also selected 3, so those messages did not share a second. This hand test shows no regression. It does not show the fixed tie. - The same-second tie is covered by the new test in `use-message-selection.test.tsx`, which fails on `main`. 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>
wesbillman
pushed a commit
that referenced
this pull request
Nov 5, 2026
…b-minimum window (#2405) Scope: fix how the main window restores its saved size after the monitor it was saved on is unplugged and the remaining monitor has another scale. Non-goals: storing the scale in the state file (tauri-plugin-window-state stays the only writer), plugin version changes, onboarding geometry. Production diff is 34 added and 10 removed lines in `src-tauri/src/window_state.rs`, plus tests. The case with several monitors left needs more logic and is handled in a follow-up PR that builds on this one. It and the remaining limitations are listed under Known gaps. ## What was wrong Quit Buzz with the window on a 1x external monitor, unplug it, and relaunch on a 2x laptop. The window comes back at half size, at or below the 480x400 minimum. The plugin then saves that small size, so later launches keep it. ## Why The plugin saves size in physical pixels. A 602x470 window saved at 1x is 301x235 points at 2x, below `minWidth`/`minHeight` in `tauri.conf.json`. macOS does not clamp a programmatic resize to the minimum (`tao` `platform_impl/macos/util/async.rs:88-101`), so it can open below it; a later user resize is clamped to it. - When no monitor contains the saved rect, `reachable_client` (`src-tauri/src/window_state.rs:215-220` on `main`) moves the window onto the primary monitor but keeps the physical size. - When the external sat to the right of the laptop, the saved rect can still fall inside the laptop's physical rect (3024 px wide at 2x), so it counts as reachable and is restored as saved. - In the fallback case, the planned rect is in the fallback monitor's physical pixels, but tao's macOS and GTK setters convert physical values with the window's current scale (`tao` 0.37.2 `platform_impl/macos/window.rs:702-731`, `platform_impl/linux/window.rs:472-510`). When Buzz starts on a monitor with another scale, the window lands at the wrong size and place. Separately, `restore_geometry_from` resized before it moved (`window_state.rs:158-159` on `main`). On Windows, moving to a monitor with another DPI rescales the window (`tao` `platform_impl/windows/event_loop.rs`, `WM_DPICHANGED`), which undoes a resize sent first. The plugin's own `restore_state` moves first. ## What changed - `restore_request` gets the fallback monitor's scale and the main window config, and stays pure. If the saved size is below the configured minimum at that scale, it was saved at a lower scale, and it gets the configured default size (1200x800) instead, still capped to the work area and horizontally centered at its top. The minimum is converted to pixels with the rounding tao uses. - On Windows tao applies the minimum to Buzz's undecorated frame, and the shadow insets come off the saved client (`tao` `platform_impl/windows/event_loop.rs:1783-1810`, `:2124-2130`), so a window at the minimum is saved below it (464x391 at 100%). The check there compares with that client: the rounded minimum minus the insets `calculate_insets_for_dpi` computes (`windows/util.rs:419-447`) with default metrics (4 px size frame plus 4 px padded border, scaled by DPI) and the Windows 11 top inset. - Windows 10 has no top inset, so its client is taller and still passes (a Windows 10 save up to that one pixel short of its own client also passes). Anything below the Windows 11 client is replaced, since tao does not clamp a programmatic resize on Windows either (it answers `WM_WINDOWPOSCHANGING` without `DefWindowProc`, `windows/event_loop.rs:1145`). - A sub-minimum size is replaced when the saved rect is unreachable, and also when only one monitor is left, since that monitor's scale is known even if the rect still falls inside it. With more monitors attached, a reachable rect is kept as saved. - A relocated window is sent in the fallback monitor's logical units on macOS and Linux. Windows keeps physical values, since its monitor and window positions are true physical pixels. - `set_position` now runs before `set_size`. ## Known gaps Handled in a follow-up PR that builds on this one (opens after this one merges): - Two or more monitors left after unplugging, with the saved rect still inside one of them. Example: a 2x laptop and a second 2x monitor remain after a 1x external is unplugged. The rect stays reachable, so this PR keeps it as saved and it opens at half size, at or below the minimum. The follow-up keeps a rect only if a monitor under its header has a scale at which the saved size meets the minimum, and still keeps a header that spans a 1x and a 2x monitor. Not fixed here or in the follow-up: - When the saved monitor is gone, its scale is unknown, so only a size that would fall below the minimum is replaced. A window saved at 960x800 pixels or larger on a 1x monitor is still at least the minimum at 2x and opens at half its points; the default 1200x800 opens at 600x400 points. A 2x window resized to 600x400 points saves the same numbers, so the two cannot be told apart. - Exact geometry when the saved monitor is still attached, the saved rect is reachable, and its scale differs from the monitor Buzz starts on. On macOS each monitor's physical rect is its point rect times its own scale, so rects overlap when scales differ. A 1x external to the right of a 2x laptop overlaps the laptop's 0 to 3024 px range, and a saved rect there has two valid readings, one per monitor. Monitor rects alone cannot tell which one was meant without the scale at save time, which only the plugin could store. - A Windows machine with a wider window border than the default (user-wide `SM_CXSIZEFRAME` / `SM_CXPADDEDBORDER`) saves a smaller client at the minimum, so a window dragged to the minimum there is reset to the default size. Reading tao's `get_frame_thickness` would cover it; this PR assumes the default. ## How verified - New tests: - `saved_size_is_kept_with_monitors_attached_and_recovered_on_a_lone_monitor` - `fallback_uses_default_size_when_saved_size_is_below_minimum_at_its_scale` - `minimum_allows_windows_shadow_insets_but_a_lower_scale_size_is_replaced`: Windows minimum clients at 100%, 125% and 200% are kept only on Windows, and one pixel under them is replaced everywhere. A 1x save of 900x750 on a 2x laptop is replaced everywhere. At 110 and 100 DPI the rounded minimum and the Windows client are checked on both sides. A minimum saved one step lower, including 225% to 250%, is replaced everywhere. - Existing `restore_request` tests now also check whether the window was relocated and at which scale. - Removing the lone-monitor rule, the default size, the fallback scale in the result, the rounding, either Windows inset, or the top inset each makes at least one of them fail. - The Windows branch was also run on macOS by forcing `cfg!(windows)` to true; CI runs only the host's branch. - `bin/cargo test -p Buzz --lib --locked window_state -- --skip onboarding_snapshot_is_kept_until_next_launch_restore`: 15 passed. That skipped test segfaults on macOS on unmodified `main` too. It calls `ns_window()` through `MockRuntime`. - `bin/cargo clippy -p Buzz --lib --tests --locked -- -D warnings`, `bin/cargo fmt --all --check` and `git diff --check` pass. - MockRuntime geometry setters are no-ops, so neither the call order nor the logical-unit conversion has automated coverage. The in-app check below covers the macOS fallback path. The Windows inset values follow tao's code and the default metrics but were not measured on a Windows machine. ## In-app check Build: this PR at `94bc460e5` (later commits only rerun CI and merge `main`; no code change), #2402, #2403 and #2404, merged locally onto `main` at `cb8c971e9`. Run as the macOS desktop app with `BUZZ_DEV_VIEWER= just desktop` on a 2x laptop with two 1x external monitors. Window bounds were read from the window server (`CGWindowListCopyWindowInfo`), not by eye. - Setup: I quit with the window on a 1x external. The saved state was 1201x714 pixels, below the 800 pixel minimum height at 2x. Then I unplugged both externals, leaving only the 2x laptop (1728x1117 points). - First relaunch: Buzz used the one-shot onboarding snapshot instead (`onboarding-window-restore.json` is used once, then deleted). That snapshot was about 1720x1410 pixels at 1x, so the window opened at 860x705 points. That is above the minimum at half the points, the first "Not fixed here or in the follow-up" case under Known gaps. - I quit, put back the state saved on the 1x monitor, and relaunched. The window opened at 1200x800 points at (264, 33), horizontally centered under the menu bar. 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>
wesbillman
pushed a commit
that referenced
this pull request
Nov 5, 2026
…menu (#2526) Scope: after Channel actions > View channel details, leave focus on the Channel Settings tab so Escape closes it. Non-goals: the settings panel's own Escape handling (#2402), the shared menu, and the `finalFocus` helper. Reuses the menu's existing `finalFocus` ref, which Move channel > Create new already sets. One file, 12 production lines added and 1 removed. ## What was wrong In the macOS app, opening View channel details from the Channel actions (…) menu with the mouse left focus on the … button. Escape then ran the Escape mark-read shortcut instead of closing the settings tab. Escape closed the tab only after Tab moved focus into the panel. ## Why The menu item only called `openDetails()` (`src/bundled/channels/ChannelHeaderMenu.tsx:88` on `main`). The menu's `finalFocus` then returns focus to the trigger when the popup unmounts after its exit animation. `ChannelsPage` (`src/bundled/channels/ChannelsPage.tsx:1756`) and `PanelWorkspace` (`src/features/panels/PanelWorkspace.tsx:298`) focus the settings tab first. But `useFinalFocusUnlessMoved` counts focus on the page body or in the closing menu as "not moved" (`src/shared/design-system/ui/finalFocus.ts:59-65`). In WebKit the tab can lose focus to the body before the menu unmounts, so the trigger wins. With focus in the main pane header, the side pane's Escape handler (`PanelWorkspace.tsx:376`) never sees the key. `docs/channels.md:412-414` says View channel details opens or focuses the Channel Settings tab. The fix sets `finalFocus.current` for that item to the settings tab (`[data-panel-workspace] [data-tab-value="settings"]`, the selector `ChannelsPage` already uses). If no such tab exists, it falls back to the trigger as before. The shared "moved" check is unchanged, so focus the person moved elsewhere still stays put. ## How verified - New test in `src/bundled/channels/ChannelHeaderMenu.test.tsx`, "hands focus to the opened settings tab when the menu finishes closing after focus drops to the body". It mounts the real menu and a `PanelWorkspace` with a closable settings tab, holds the menu's exit with a `getAnimations` stub, clicks the item with `userEvent`, and blurs to the body before the menu unmounts. Then it asserts the tab has focus and Escape calls `close`. It fails on `main` with focus on the Channel actions button and passes with the fix. The path where focus stays on the tab is already covered by `tests/browser/channel-settings.spec.mjs` and `channel-header-menu.spec.mjs` in Chromium and WebKit. - `bin/pnpm vitest run` on `ChannelHeaderMenu`, `PanelWorkspace`, `ChannelSettingsPanel`, and the five `ChannelsPage*` test files: 144 passed. - `bin/just iterate`: passed on the first commit. The second commit changes only the test. - jsdom and the Playwright click do not reproduce the WebKit focus drop, so the test forces it with the blur. - Hook exception, with my OK: I pushed with `LEFTHOOK_EXCLUDE=types-and-related-unit-tests`, so that one pre-push job was skipped and the other pre-push jobs ran. On the current head that job's type check passed and its full Vitest run failed only two `ChannelMembersDialog.test.tsx` tests (2 failed, 17351 passed). Those two fail the same way at `40401e4d8` without this change, both locally and in `main` CI (`JavaScript (4/4)`). ## In-app check Found in hand testing on the macOS app, and verified there with this fix. Build: this PR at `75fef69f8` (later commits only rerun CI and merge `main`; no code change) with #2402, #2403, #2404 and #2405, merged locally onto `main` at `cb8c971e9`. Run as the macOS desktop app with `BUZZ_DEV_VIEWER= just desktop`. 1. In a regular channel, open Channel actions (…) > View channel details with the mouse. 2. Press Esc right away, without clicking anything else or pressing Tab. 3. The Channel Settings tab closes. Before the fix, it stayed open and Esc marked the conversation as read. buzz-review-completed --------- Signed-off-by: Cheng Peng <cheng@block.xyz> Co-authored-by: Cursor <cursoragent@cursor.com>
wpfleger96
added a commit
that referenced
this pull request
Nov 5, 2026
…-service * origin/main: (29 commits) fix(agents): treat what an agent reads as information, and give answering rules only to mentions (#2456) feat: add up arrow message editing preference (#2659) fix(channels): move focus to channel settings opened from the header menu (#2526) fix(window): restore position before size and never fall back to a sub-minimum window (#2405) fix(messages): pick the latest message with the shared ordering (#2403) fix(channels): let shift+escape through the channel settings panel (#2402) fix(builderlab): clarify signed-out agent settings (#2602) feat(builderlab): preselect the catalog's recommended model (#2552) feat(host-process): set BUZZ_VERSION for every spawned process (#2674) fix(agents): finish Delete for a synced agent with no config (#2692) ci(flake-check): select related tests within a 9-minute lane budget (#2607) feat(macos): show the closed window again when Buzz becomes active (#2658) fix(channels): show channel actions as soon as the menu opens (#2657) fix(huddles): confirm before starting a huddle from the header (#2687) perf(composer): reuse per-keystroke projections, snapshots and reference scans (#2590) perf(agents): skip the activity snapshot compare for unrelated channel traffic (#2586) test(solo): wait for the live header before opening a joined viewer's access (#2599) fix(messages): show who reacted in channels where you can't react (#2601) feat(agents): clarify remote execution across desktop (#2600) perf(relay): size paged history with the cached per-event sizes (#2597) ... # Conflicts: # src-tauri/src/host_process/tests.rs
wesbillman
pushed a commit
that referenced
this pull request
Nov 5, 2026
Scope checkpoint: end bare `http(s)://` and `buzz://` links at fullwidth and CJK punctuation in rendered messages and in `messageLinkParts`, keep CJK letters inside URLs, and leave explicit `<url>` and `[label](url)` links unchanged. Non-goals: links without a scheme (`www.`), Markdown link syntax, link previews. Files: `message-link-parts.ts`, `MessageMarkdown.tsx`, their tests, and one line in `docs/buzz-help.md`. Budget: about 30 production lines (actual +30/−1). ## What was wrong A bare link followed by Chinese or Japanese punctuation took the punctuation and any text after it into the link. `https://github.com/block/buzz-app。` opened `…/buzz-app%E3%80%82` (404). `看这个 https://example.com/spec,大家看一下` linked the whole rest of the sentence. `(https://example.com)` opened the host `example.com)`, because the URL parser folds `)` to `)`. ## Why Two places find bare URLs, and both end them only at whitespace: - Rendered messages get bare links from `remark-gfm`. Its `findUrl` transform (`mdast-util-gfm-autolink-literal` `lib/index.js:151`) reads up to the next space and trims only ASCII punctuation (`splitUrl`, line 241). `messageLinkParts` never sees these URLs; it only gets the text around them. - `messageLinkParts` (`src/features/messages/message-link-parts.ts:98`) drives the composer link decorations, chat cards and clipboard. Its bare URL pattern is `` [^\s<>"`]+ ``, so the same input fails there too. ## How verified - `message-link-parts.ts:98`: the bare URL pattern now also stops at fullwidth and CJK punctuation (`,。、.!?;:"'()[]{}「」『』《》【】〈〉〔〕〖〗〝〞〟。「」、…~〜`) and curly quotes (`“”‘’`), which Chinese text uses as its quotes. CJK letters are still allowed, so `https://zh.wikipedia.org/wiki/中文` stays whole. - In the host name (before any `:`, `/`, `?` or `#`), `。.。` followed by an ASCII host label continue the link (unless another `://` follows that label), because the URL parser reads them as `.`: `https://github。com/block/buzz-app` still opens github.com, as on `main`. After the host name, including after a port, they end it. `\s` already covers the ideographic space. Bracket pairs need no extra trimming, because the match stops before them. - `MessageMarkdown.tsx:342`: a small remark step runs after `remark-gfm`. When `messageLinkParts` splits a literal autolink differently, the step replaces it with the parts `messageLinkParts` finds, so `https://a.com,https://b.com` becomes two links with the `,` between them as text. Links whose source is `<url>` or `[url](url)` keep their written destination. - `docs/buzz-help.md`: one bullet says where a bare http(s) or buzz link ends and that `<url>` keeps those characters. - New tests: `message-link-parts.test.ts` "ends a bare URL at fullwidth punctuation but keeps CJK letters" (18 cases, plus one that `https://a.com。https://b.com` gives two links), and `MessageMarkdown.test.tsx` "ends a bare link at fullwidth punctuation" (9 cases, checks the rendered `href`). 23 of the 28 fail on `main`. The other 5 guard IRIs, `<url>` and hosts written with `。`. - `bin/pnpm vitest run src/features/messages/message-link-parts.test.ts src/features/messages/MessageMarkdown.test.tsx`: 175 passed after the latest merge with `main`. - `bin/pnpm vitest run src/features/messages src/features/conversation src/features/relay src/bundled/links src/bundled/relay-notes`: 5574 passed, 4 skipped. - `bin/just iterate` (format, lint, typecheck, build): passed. - Known limits: GFM reads Markdown link syntax glued to a bare URL (`https://a.com,[label](https://b.com)`) as part of that URL, so the brackets still show, as they do on `main`. A bare URL that really contains one of these characters, such as a typographic `’` in a title, now ends there; `<url>` keeps it whole. A host followed by `。` and an English word (`https://example.com。OK`) still reads the word as part of the host, as on `main`. ## In-app check Build: this PR at `2dd13819d`, the SHA I tested by hand (the branch was later merged with `main` to resolve conflicts in `docs/buzz-help.md`; no behavior change), with #2402, #2403 and #2405, merged locally onto `main` at `cb8c971e9`. Run as the macOS desktop app with `BUZZ_DEV_VIEWER= just desktop`. I sent these nine lines as one message in a Solo chat. The link on each line covered exactly this text: | # | Line | Linked text | | --- | --- | --- | | 1 | `https://github.com/block/buzz-app。` | `https://github.com/block/buzz-app` | | 2 | `看这个 https://example.com/spec,大家看一下` | `https://example.com/spec` | | 3 | `https://a.com,https://b.com` | `https://a.com` and `https://b.com`, two links | | 4 | `https://zh.wikipedia.org/wiki/中文` | the whole URL | | 5 | `(https://example.com)` | `https://example.com` | | 6 | `<https://example.com/a。b>` | the whole URL | | 7 | `https://github。com/block/buzz-app` | the whole URL | | 8 | `http://localhost:3000。OK` | `http://localhost:3000` | | 9 | `https://github。com:/block/buzz-app` | the whole URL | Clicking lines 1 and 7 opened github.com/block/buzz-app, with no `%E3%80%82` in the address. ## Screenshots The same message in the installed 0.7.8 release and in the build above. In 0.7.8, lines 1, 2, 3 and 5 take the punctuation into the link, and line 8 is not linked at all. | Before (0.7.8) | After (this PR) | | --- | --- | | <img width="360" alt="Before, Buzz 0.7.8: the nine test lines, with punctuation inside links on lines 1, 2, 3 and 5 and line 8 not linked" src="https://github.com/user-attachments/assets/064dd7ff-cde4-476e-887c-7310288254d2" /> | <img width="360" alt="After, this PR: the nine test lines, with each link covering the text listed in the table above" src="https://github.com/user-attachments/assets/9569922c-3365-4df4-97ae-b469faab0637" /> | buzz-review-completed --------- Signed-off-by: Cheng Peng <cheng@block.xyz> Co-authored-by: Cursor <cursoragent@cursor.com>
baxen
added a commit
that referenced
this pull request
Nov 6, 2026
* origin/main: (36 commits) perf(mentions): rank only the shown rows and skip empty-query Space work (#2298) fix(messages): stop bare links at fullwidth and CJK punctuation (#2404) fix(agents): treat what an agent reads as information, and give answering rules only to mentions (#2456) feat: add up arrow message editing preference (#2659) fix(channels): move focus to channel settings opened from the header menu (#2526) fix(window): restore position before size and never fall back to a sub-minimum window (#2405) fix(messages): pick the latest message with the shared ordering (#2403) fix(channels): let shift+escape through the channel settings panel (#2402) fix(builderlab): clarify signed-out agent settings (#2602) feat(builderlab): preselect the catalog's recommended model (#2552) feat(host-process): set BUZZ_VERSION for every spawned process (#2674) fix(agents): finish Delete for a synced agent with no config (#2692) ci(flake-check): select related tests within a 9-minute lane budget (#2607) feat(macos): show the closed window again when Buzz becomes active (#2658) fix(channels): show channel actions as soon as the menu opens (#2657) fix(huddles): confirm before starting a huddle from the header (#2687) perf(composer): reuse per-keystroke projections, snapshots and reference scans (#2590) perf(agents): skip the activity snapshot compare for unrelated channel traffic (#2586) test(solo): wait for the live header before opening a joined viewer's access (#2599) fix(messages): show who reacted in channels where you can't react (#2601) ... # Conflicts: # docs/buzz-help.md
nathan-thillairajah
added a commit
that referenced
this pull request
Nov 6, 2026
…page * origin/main: (49 commits) fix(browser): recheck the FI session when the app viewer exchange returns 401 (#2678) fix(members): find verified people who have no profile (#2554) fix(dev): clear React's dev render measures so long sessions do not grow (#2495) Move app navigation to a dock and unify conversation headers (#2626) perf(mentions): rank only the shown rows and skip empty-query Space work (#2298) fix(messages): stop bare links at fullwidth and CJK punctuation (#2404) fix(agents): treat what an agent reads as information, and give answering rules only to mentions (#2456) feat: add up arrow message editing preference (#2659) fix(channels): move focus to channel settings opened from the header menu (#2526) fix(window): restore position before size and never fall back to a sub-minimum window (#2405) fix(messages): pick the latest message with the shared ordering (#2403) fix(channels): let shift+escape through the channel settings panel (#2402) fix(builderlab): clarify signed-out agent settings (#2602) feat(builderlab): preselect the catalog's recommended model (#2552) feat(host-process): set BUZZ_VERSION for every spawned process (#2674) fix(agents): finish Delete for a synced agent with no config (#2692) ci(flake-check): select related tests within a 9-minute lane budget (#2607) feat(macos): show the closed window again when Buzz becomes active (#2658) fix(channels): show channel actions as soon as the menu opens (#2657) fix(huddles): confirm before starting a huddle from the header (#2687) ... Signed-off-by: Nathan Thillairajah <nthillairajah@squareup.com> # Conflicts: # docs/browser.md # src-tauri/src/browser/builderlab.rs
nathan-thillairajah
added a commit
that referenced
this pull request
Nov 6, 2026
…dylan/browser-unavailable-page * origin/dylan/browser-denied-page: (49 commits) fix(browser): recheck the FI session when the app viewer exchange returns 401 (#2678) fix(members): find verified people who have no profile (#2554) fix(dev): clear React's dev render measures so long sessions do not grow (#2495) Move app navigation to a dock and unify conversation headers (#2626) perf(mentions): rank only the shown rows and skip empty-query Space work (#2298) fix(messages): stop bare links at fullwidth and CJK punctuation (#2404) fix(agents): treat what an agent reads as information, and give answering rules only to mentions (#2456) feat: add up arrow message editing preference (#2659) fix(channels): move focus to channel settings opened from the header menu (#2526) fix(window): restore position before size and never fall back to a sub-minimum window (#2405) fix(messages): pick the latest message with the shared ordering (#2403) fix(channels): let shift+escape through the channel settings panel (#2402) fix(builderlab): clarify signed-out agent settings (#2602) feat(builderlab): preselect the catalog's recommended model (#2552) feat(host-process): set BUZZ_VERSION for every spawned process (#2674) fix(agents): finish Delete for a synced agent with no config (#2692) ci(flake-check): select related tests within a 9-minute lane budget (#2607) feat(macos): show the closed window again when Buzz becomes active (#2658) fix(channels): show channel actions as soon as the menu opens (#2657) fix(huddles): confirm before starting a huddle from the header (#2687) ... Signed-off-by: Nathan Thillairajah <nthillairajah@squareup.com> # Conflicts: # src-tauri/src/browser/builderlab.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'sonKeyDowninsrc/bundled/channels/ChannelSettingsPanel.tsx:101closes on anyEscapefrom inside the panel and callsevent.stopPropagation(). Since #2506 it skips events that are already handled or come from a portalled layer, but it still has no!event.shiftKeycheck. 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.shiftKeyguard and comment the siblings use, next to the #2506 checks. Plain Esc still closes the panel.How verified
src/bundled/channels/ChannelSettingsPanel.test.tsx, "closes with Escape or its close button without swallowing other keys": ⇧Esc does not callcloseand reaches awindowkeydown 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 mergingmain, including fix(channels): render the channel details description as linked Markdown with a label-only Edit target #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 ontomainatcb8c971e9. Run as the macOS desktop app withBUZZ_DEV_VIEWER= just desktop. The branch was later merged withmaintwice 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.
Where focus goes after opening the panel from the menu is a separate issue.
buzz-review-completed