Skip to content

feat(inbox): preview recent Inbox activity when hovering the sidebar row - #2347

Merged
gilgtm merged 8 commits into
mainfrom
gil/inbox-hover-preview
Nov 4, 2026
Merged

gilgtm merged 8 commits into
mainfrom
gil/inbox-hover-preview

Conversation

@gilgtm

@gilgtm gilgtm commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Hovering the Inbox row in the desktop sidebar now opens a small card with your most recent Inbox conversations, so you can check what's waiting without leaving the conversation you're in. The card lists up to 50 recent conversations with sender, place, time and unread state. It has its own Unread only switch, which never changes the Inbox page's saved filters. Clicking a row opens that conversation directly, and Open Inbox opens the full page. The card opens on hover with a fine pointer only. Pressing or keyboard-activating the row still selects the page, and the card is not shown while the Inbox page is already selected.

The shell owns the trigger, hover timing, placement and dismissal through a new optional preview slot on registered pages (PagePreviewProps in src/features/pages/service.ts, a FOUNDATION file, edited with approval). The Inbox plugin supplies only the content (InboxPreview). It reuses the page's unread, feed, profile and archive state, and InboxRow is extracted so the page and card render rows the same way. The card sits 6px to the right of the row's hover background, with the line under its header level with the row's top. EmptyState gains a frame="none" option for empty states inside a card. Failure, retry, loading and empty handling match the page: connection, activity and read-state sync errors show with Retry, and an empty result shows only after activity was checked.

Inbox hover card beside the sidebar row, light theme
Inbox hover card with Unread only on, dark theme

Browser coverage

  • Added tests/browser/inbox-preview.spec.mjs (one case, Chromium and WebKit). It covers what jsdom cannot: real hover-intent timing, Base UI positioning against the rendered row, and the card's measured placement. At widths of 1280px and above it asserts the 6px gap and header alignment, measured after the open animation finishes. Narrower layouts must place the card beside or below the row. Hover behavior, focus handoff, press/focus guards, filter isolation, open targets, and failure/retry paths are covered in Vitest (AppShell.test.tsx, InboxPage.test.tsx, service.test.ts). No browser cases were removed. Each new assertion was checked to fail with its fix reverted.

Testing

  • Agent: Chromium and WebKit through the Playwright fixture app.
  • Author: tested the hover card in the running desktop app. After the Retry fixes, re-tested both in the fixture app: Retry moves focus to Unread only, and one Retry recovers a failed channel list.
  • Known issue, not caused by this PR: when the channel list fails and Retry is clicked a few seconds later, the card and the Inbox page show "Relay read cancelled" until a second Retry. The same happens on main. A separate PR will fix it.

buzz-review-completed

gilgtm and others added 4 commits November 3, 2026 15:51
Resting a hover pointer on the sidebar Inbox row opens a card with the
newest Inbox conversations, an unread summary and an Unread only switch
that is separate from the Inbox page's filters. Clicking a row opens its
conversation directly; Open Inbox goes to the page. The card is not shown
while the Inbox page is selected.

The shell owns the trigger, hover timing, placement and dismissal through
a new optional page `preview` contribution; Inbox supplies only the
content. Inbox rows are shared between the page and the card.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Gil Birman <[email protected]>
- Re-ensure the card's reads after a discovery revision retires a feed
  attempt, as the Inbox page does; the card could otherwise stay idle.
- Include read-state sync failures in the card's failure and retry sync
  after refreshing, as the page does.
- Show "Nothing unread" and the empty states only once activity was
  checked, not while it is idle.
- Mark the shell's hover trigger as a transient preview so it does not
  hold back a conversation's default composer focus.
- Cover the press/focus guard, focus handoff, the full conversation
  target, page filter independence, activity/connection retry and the
  idle state; wait for the open transition before measuring placement.

Validation: tsc; vitest src/app/shell src/bundled/inbox src/features/pages
src/plugins/author.test.mjs (542 passed); playwright inbox-preview.spec
chromium+webkit --repeat-each 3; each new assertion fails with its fix
reverted.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Gil Birman <[email protected]>
Base UI popover triggers never open on focus, so drop the dead trigger-focus guard and the focus step that tested nothing. Cover the card's read-state sync failure and retry with a fixture hook, so dropping sync.error from the card fails the test.

Validation: tsc; biome; vitest src/app/shell src/bundled/inbox src/features/pages src/plugins/author.test.mjs (542 passed); mutation check on sync.error.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Gil Birman <[email protected]>
Return the toolbar button first, as before this branch, so toolbar pages no longer build and discard a sidebar row. Behavior is unchanged.

Validation: tsc; vitest src/app/shell.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Gil Birman <[email protected]>
@gilgtm
gilgtm marked this pull request as ready for review November 4, 2026 01:51
@gilgtm
gilgtm requested a review from a team as a code owner November 4, 2026 01:51

@kazemek21 kazemek21 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.

Kyle reviewed this PR and the Nexus review-tracker notes. Approved with the two non-blocking retry comments below.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

Comment thread src/bundled/inbox/InboxPreview.tsx Resolved
Comment thread src/bundled/inbox/InboxPreview.tsx Resolved
gilgtm and others added 2 commits November 4, 2026 06:28
A roster Retry now also refreshes activity that failed meanwhile, so
one Retry clears both errors. When a focused Retry recovers and leaves
the card, focus moves to the Unread only switch instead of the body.

Co-authored-by: Claude <[email protected]>
Signed-off-by: Gil Birman <[email protected]>
* origin/main: (52 commits)
  feat(agents): give plugin agent types the Buzz side of a turn (#2364)
  feat(plugins): pin buzz://plugin-install links to a path and commit (#2315)
  feat(plugins): tint currentColor SVG page icons like host icons (#2314)
  feat(notifications): add a soft notification sound (#1536)
  feat(agents): let agents propose a profile picture (#2267)
  fix(plugins): default reminders and workflows off for fresh installs (#2291)
  Show all reactors in compact, separate emoji capsules (#2179)
  fix(panels): remember page split widths per workspace (#2264)
  feat(search): browse open channels from a Channels tab in search (#2164)
  perf(relay): page combined replay filters after a reconnect (#2308)
  fix(composer): make native Mac text shortcuts work past link chips (#2390)
  perf(relay): read creator proofs for many channels per filter (#2312)
  fix(channels): let published leave and delete confirm through access loss (#2357)
  fix(media): save native context menu media downloads and hide loopback links on macOS (#2131)
  fix(links): say why a message preview is unavailable, and preview unjoined public channels (#2136)
  test(browser): serve emoji artwork in the reaction hit-area journey (#2138)
  test(browser): click a reply link only after its row settles (#2358)
  fix(agents): restore delegated workflow mentions (#2399)
  feat(identity): always back up keys in managed builds (#2398)
  test(threads): render before checking the reader across activity resizes (#2389)
  ...

Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Claude <[email protected]>

# Conflicts:
#	src/plugins/author.test.mjs
@gilgtm
gilgtm marked this pull request as ready for review November 4, 2026 19:57
gilgtm and others added 2 commits November 4, 2026 14:03
* origin/main: (22 commits)
  feat(mentions): quieter mention menu that marks agents and their owners (#2328)
  feat: add a Canvas View tab, History diffs and a 256 KiB limit (#2318)
  feat(plugins): let local agents write and reload plugins (#2435)
  feat(onboarding): offer Create a community after public Builderlab sign-in (#2397)
  Open any Inbox filter view by route (#2394)
  fix(bestie): deleting an agent saves no Bestie record where there was none (#2420)
  fix(unread): never treat agents-audience messages as unread (#1552)
  feat(focus): add an optional Focus plugin that quiets everything except Solo chats (#2277)
  [5/5] Add protection to the Goose harness plugin (#839)
  feat(agents): identify plugins in native hook output (#2299)
  [4/5] Add protection to the Codex harness plugin (#1879)
  feat(search): show member counts on channel results (#2333)
  Fix the mute icon flashing when selecting a channel (#2287)
  feat(agents): open a working agent's transcript from its activity (#2276)
  test(agents): refuse a real relay workflow mention in the harness kit (#2444)
  fix(agents): catch up every event watch on the kinds it watches (#1852)
  Keep local artifact collections current (#1507)
  feat(members): add several people to a chat in one write (#2010)
  feat(solo): let viewers join read-only spaces (#2301)
  [3/5] Add protection to the Claude harness plugin (#1878)
  ...

Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Claude <[email protected]>
* origin/main:
  fix: exclude muted channels from sidebar unread scroll indicators (#2335)
  fix(claude-code): open long Claude transcripts a page at a time (#2428)
  feat(solo): share chats as view only (#2375)
  fix(inbox): show Solo responses under All, read live replies in the open conversation, describe toolbar badges (#2275)
  AIDA-2434: Show saved teams in mentions (#2339)
  AIDA-2455: Restore channel scrollbar dragging beside threads (#2436)
  feat(agent-protection): show the owner why a protection provider denied input (#2105)
  fix(search): show slow lookups beside their section headings (#2442)
  Align Bestie DM layout and unread state; speed up thinking shimmer (#2418)
  Send Claude, Pi and Goose history as exact records (#2088)
  fix(agent-activity): align the stopped note and show it after the agent's reply (#2441)
  fix(agents): stop orphaned Attention commands and prevent duplicate runs (#2448)

Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Claude <[email protected]>

# Conflicts:
#	src/app/shell/PageNavigation.tsx
@gilgtm
gilgtm merged commit ccf922b into main Nov 4, 2026
42 checks passed
@gilgtm
gilgtm deleted the gil/inbox-hover-preview branch November 4, 2026 21:15
wpfleger96 added a commit that referenced this pull request Nov 4, 2026
…-nip-fi

* origin/main:
  feat(inbox): preview recent Inbox activity when hovering the sidebar row (#2347)
  Add collapsible Solo and Team sidebar sections (#2296)
  fix(codex): open long Codex transcripts a page of items at a time (#2259)
  Keep Solo model and effort choices per chat (#2342)
  feat(sidebar): smooth channel reordering with bounded motion (#2108)
  Connect legacy agents from DMs and channel mentions (#2423)
  fix: exclude muted channels from sidebar unread scroll indicators (#2335)
  fix(claude-code): open long Claude transcripts a page at a time (#2428)
  feat(solo): share chats as view only (#2375)

# Conflicts:
#	src/app/shell/PrimarySidebar.tsx
damienrj added a commit to damienrj/buzz-app that referenced this pull request Nov 4, 2026
…-clone-auth-ab8b97

* origin/main: (38 commits)
  feat(identity): decide a missing backup choice from community membership (block#2378)
  Offer Builderlab decisions in FI builds (block#2227)
  feat(inbox): preview recent Inbox activity when hovering the sidebar row (block#2347)
  Add collapsible Solo and Team sidebar sections (block#2296)
  fix(codex): open long Codex transcripts a page of items at a time (block#2259)
  Keep Solo model and effort choices per chat (block#2342)
  feat(sidebar): smooth channel reordering with bounded motion (block#2108)
  Connect legacy agents from DMs and channel mentions (block#2423)
  fix: exclude muted channels from sidebar unread scroll indicators (block#2335)
  fix(claude-code): open long Claude transcripts a page at a time (block#2428)
  feat(solo): share chats as view only (block#2375)
  fix(inbox): show Solo responses under All, read live replies in the open conversation, describe toolbar badges (block#2275)
  AIDA-2434: Show saved teams in mentions (block#2339)
  AIDA-2455: Restore channel scrollbar dragging beside threads (block#2436)
  feat(agent-protection): show the owner why a protection provider denied input (block#2105)
  fix(search): show slow lookups beside their section headings (block#2442)
  Align Bestie DM layout and unread state; speed up thinking shimmer (block#2418)
  Send Claude, Pi and Goose history as exact records (block#2088)
  fix(agent-activity): align the stopped note and show it after the agent's reply (block#2441)
  fix(agents): stop orphaned Attention commands and prevent duplicate runs (block#2448)
  ...

Signed-off-by: damienrj <[email protected]>
matt2e added a commit that referenced this pull request Nov 4, 2026
…2395)

## What users see

Someone mentions you in a channel you haven't opened this session, and
their profile hasn't loaded yet. You leave any channel and open the
**Inbox**. The row shows a shortened key (`npub…srl`) and a letter
avatar instead of their name. It stays that way while the Inbox is open,
and nothing asks again. Their name appears only after you open their
channel.

This is a regression from #2269 ("reserve profile room for an open
channel's roster", by Logan Johnson, `loganj`). A bisect over 248
commits, with no skips, found its merge commit `fe6eaa222` as the first
bad one. Its parent `589c2a489` names the author right away. The channel
being left doesn't need to be large: in the repro, #Alpha has one
member.


https://github.com/user-attachments/assets/e1e24cfb-14c1-4a4c-ba65-993443e28b5c

Left: the branch base 8668dbd. Right: this branch at b3d1f09. Both
sides click Inbox on the same frame. On the left, the Inbox's read for
Priya is cancelled 6 ms after it's sent and the row stays `npub…srl`. On
the right, the read returns 200 in 16 ms and the row shows "Priya Shah"
from the first Inbox frame.

![Three seconds after clicking Inbox: the base build (red, left) still
shows npub…srl with a letter avatar; the fix (green, right) shows Priya
Shah with her
avatar](https://github.com/user-attachments/assets/f378e271-14b3-4921-bd45-516340b00c47)

## Cause

1. An open channel reserves profile-cache room for its roster
(`Conversation.tsx`, a layout effect). Leaving releases that
reservation, and the release schedules the shrink one task later
(`shrinking ??= setTimeout(resize)`).
2. In the same commit, the Inbox's mount effect asks for the names it
shows, as a background read.
3. When `resize()` runs, it treats the shrink (1025 → 1024 in the repro)
as a reason to cancel **every** pending background read that isn't a
reconnect refresh. That includes the read the Inbox just started.
4. The cancel is silent: `ensure()` resolves normally, and only
`evictions()` moves. The mention menu is the only consumer of
`evictions()`. The Inbox's effect has no dependency that changes, so it
never asks again.

The cancel in #2269 was right for the departing view's reads, for
example the mention menu's 500-author roster pages, which would
otherwise land late and push the next view's names out of the
1,024-entry cache. It was wrong for reads the **next** view started
while the shrink was waiting.

## The fix

The fix is in one production file,
`src/features/relay/profile-directory.ts` (+28/−8):

- **Stamp at creation.** A counter goes up each time a reservation is
released. A read records that count when it is created, and the stamp
never changes (`readonly releases`).
- **One predicate.** `shrinkCancels(batch)` is true for a background
read that isn't a reconnect refresh and was created before the latest
release. `resize()` uses it to choose what to cancel. `ensure()` uses it
to decide whether a request may join a pending read.
- **No joining a read that's about to be cancelled.** While a shrink is
waiting, a request doesn't join a pending read that the shrink would
cancel. That name gets its own read, and the shrink still cancels the
rest of the departing page. Joins on foreground and refresh reads, and
any join while no shrink is waiting, work as before.
- A remount or a roster-count change is unaffected. `reserve()` clears
the waiting shrink before the children's effects ask for names.
- The `evictions()` doc comment, the `resize()` comment and one line of
`docs/channels.md` now describe the narrower cancel. `docs/buzz-help.md`
is unchanged, because this restores the earlier behaviour.

**Why the stamp isn't updated on a join.** The first commit (8429c27)
updated a pending read's stamp whenever a later request joined it.
Review showed that this kept a whole departing roster page alive for the
sake of one shared author. The reader runs one background job at a time,
so those pages also landed before the Inbox's own read. In a
9,000-member scenario, 3 joined pages loaded 1,500 roster profiles into
the 1,024-entry cache. That pushed out 5 names the Inbox had already
shown, and those rows went back to `npub…`, which is the case #2269 was
written to prevent. With the creation-time stamp and the join skip, the
same scenario cancels 19 pages and keeps all the Inbox's names and all
200 earlier names, with the same 20 reads.

**Rejected alternatives**
- **Make the Inbox re-read when `evictions()` moves.** That fixes one
page, and every switch from a channel to the Inbox would send the read,
cancel it and send it again.
- **Bump `generation()` when a shrink cancels reads.** That changes what
`generation()` means: it marks a full cache purge, which other code keys
on.
- **Drop the cancel, or skip it for small shrinks.** Either one brings
back the eviction case #2269 fixed.

**Accepted costs.** These were noted in review and don't block:
- A release that leaves a larger reservation open can split a join into
a second, redundant read until the next task.
- If the next view asks again for every author on a departing page, that
page isn't cancelled. It loads only names the next view asked for, so
the cost is one redundant read and no names are pushed out.

## Surfaces covered

The directory change covers every page that replaces a channel and asks
for names in its mount effect without watching `evictions()`:
- the Inbox (tested and recorded);
- Drafts, opened straight from a channel (`DraftsView.tsx`);
- Reminders (`RemindersPage.tsx`);
- Workflows owner names (`WorkflowLanding.tsx`);
- Agents → Watching (`watching/Tab.tsx`);
- the Inbox hover preview in open PR #2347, which copies the Inbox's
effect.

These were found by reading the code. Only the Inbox has a browser case
or a recording. The others use the same directory rule, which the unit
test covers.

**Out of scope.** Views that stay mounted when you leave a channel are
still exposed if their read happens to be in flight at that moment:
- sidebar DM labels (`useChannelLabels.ts`);
- Inbox names for edited-message authors (`unread.ts`);
- channel previews loaded in the background (`store.ts`), which are
re-read when the channel opens.

The window is one relay round trip at the moment you leave. None of
these was reproduced, so a follow-up is warranted only if one is.

## Tests

### Unit test (Vitest), `src/features/relay/profile-details.test.ts`

The new test, "keeps background reads asked for after a release when the
shrink runs", sits next to #2269's cancellation test, which is
unchanged.
- **Setup:** reserve room. Start two departing background reads: one for
a `stale` author, and one page holding `joined` and `other`. Release.
Before the timer fires, the next view asks for `fresh` and `joined`.
- **Assertions:**
- how each read is split: `[stale]`, `[joined, other]`, `[fresh,
joined]`;
- `evictions()` moves by exactly 2, because both departing reads are
cancelled;
- every read is answered with all four profiles, and only `fresh` and
`joined` end up in the cache.
- **Mutants:** in the round 2 review, four mutants were run against this
test at b01fe78, and each failed:
1. the first commit's stamp update on join: evictions +1, and `other` is
named;
  2. no join skip: `joined` stays unnamed;
3. a shrink that never cancels: evictions +0, and `stale` and `other`
are named;
4. main's behaviour: `fresh` and `joined` stay unnamed, with evictions
+3.

Mutants 1 and 2 issued 3 reads either way, so b3d1f09 added the
read-split assertion. With the join skip removed, the third read holds
only `fresh`, and the test now fails on the split too.

### Browser case, `tests/browser/inbox.spec.mjs`

One case was added, "Inbox names after leaving a channel › names an
author seen only in the Inbox", and none were removed.
- **The flow:** open on #Alpha, click Inbox, and expect the row for an
author seen only in #Launch to contain "Priya Shah". Also expect exactly
one kind-0 query naming her.
- **Why it needs a browser:** the bug depends on ordering across two
surfaces: the channel's layout-effect release, then the Inbox's
mount-effect read, then the deferred shrink. The unit test proves the
directory rule, not that the real Inbox's read starts after the release
and before the shrink.
- **Fail, then pass:** it fails without the fix in both Chromium and
WebKit, where the row shows `npub…`. It passed 5/5 per engine at
8429c27 and passed again in both engines after the join change.
Production code hasn't changed since then; b3d1f09 changes only the
unit test and a comment.
- **Why CI missed this:** no journey checked the name of an author seen
only in the Inbox.

### Other checks during development

These were not re-run for this push:
- `profile-details.test.ts`: 25/25 pass.
- The 16 unit test files that import the profile directory: 630/630
pass.
- `profile-roster-scale.spec.mjs`, #2269's 9,000-member check: passes in
both measurement projects.
- `typecheck`, `lint` and `git diff --check`: clean.
- Pre-commit hooks: passed on each commit. Pre-push hooks were skipped
for this push (`--no-verify`).

## Review rounds

- **Round 1 (8429c27):** one blocker. Updating the stamp on a join
kept departing roster pages alive (the 9,000-member scenario above).
Fixed in b01fe78, along with the optional items: a clearer field
comment, a `stale` assertion that can actually fail (the test now
answers every read with all profiles), and a minimal `docs/channels.md`
diff.
- **Round 2 (b01fe78):** the blocker is fixed, with no new defects,
and the change was judged at the 9/10 bar. Its optional items, asserting
how each read splits and a comment wording, are in b3d1f09.

## Status

This is a draft until a human tries it in the app (AGENTS.md "Before
opening a PR", step 3). To test: open the app on a channel, then click
**Inbox**. An author who mentioned you in a channel you haven't opened
this session should show their name and avatar right away, without
opening their channel.

Deferred:
- the human test above;
- hosted CI on this head;
- a WebKit recording (the browser case covers WebKit);
- recordings of the other covered pages.

Logan Johnson (`loganj`): this narrows #2269's cancel-on-shrink and
changes the `evictions()` contract wording. Your review is welcome.

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

---------

Signed-off-by: Matt Toohey <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
JonForest added a commit to JonForest/buzz-app that referenced this pull request Nov 4, 2026
Resolve conflicts with block#2347, which moved hasUnread into InboxRow.tsx, and
give its new inbox-preview browser journey the shared INBOX_BUTTON locator.

Co-authored-by: DeviousMachine <8aa903583ddc111fb259d28d298a0b05685db29a3082a57a99c98522ae044622@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: Jonathan Hollingsworth <[email protected]>
Signed-off-by: Jonathan Hollingsworth <[email protected]>
johnmatthewtennant added a commit that referenced this pull request Nov 4, 2026
* origin/main: (38 commits)
  fix(browser): load scheme-less address input over https, falling back to http (#2467)
  fix(identity): stop one-click key unlinks and show the linked backed-up key (#2486)
  fix(test): observe coordination transition events (#2297)
  fix(profiles): keep the next view's profile reads through a shrink (#2395)
  fix(channels): show Add and Send message errors above the Manage members list (#2421)
  fix(threads): keep reply hooks off agent activity bubbles (#2422)
  fix(channels): offer only Copy link in a not-joined preview's sidebar actions (#2425)
  test: scope row menu placement checks to active section rows (#2493)
  feat(relay): detach channel windows to a point in history (#2042)
  feat(identity): decide a missing backup choice from community membership (#2378)
  Offer Builderlab decisions in FI builds (#2227)
  feat(inbox): preview recent Inbox activity when hovering the sidebar row (#2347)
  Add collapsible Solo and Team sidebar sections (#2296)
  fix(codex): open long Codex transcripts a page of items at a time (#2259)
  Keep Solo model and effort choices per chat (#2342)
  feat(sidebar): smooth channel reordering with bounded motion (#2108)
  Connect legacy agents from DMs and channel mentions (#2423)
  fix: exclude muted channels from sidebar unread scroll indicators (#2335)
  fix(claude-code): open long Claude transcripts a page at a time (#2428)
  feat(solo): share chats as view only (#2375)
  ...
wpfleger96 added a commit that referenced this pull request Nov 4, 2026
…r-load-perf

* origin/main: (31 commits)
  test: fix invitation and coordination checks on main (#2517)
  perf(search): name searched people from the profile search response (#2353)
  Use the merge queue for pull requests that require it (#2452)
  fix(browser): load scheme-less address input over https, falling back to http (#2467)
  fix(identity): stop one-click key unlinks and show the linked backed-up key (#2486)
  fix(test): observe coordination transition events (#2297)
  fix(profiles): keep the next view's profile reads through a shrink (#2395)
  fix(channels): show Add and Send message errors above the Manage members list (#2421)
  fix(threads): keep reply hooks off agent activity bubbles (#2422)
  fix(channels): offer only Copy link in a not-joined preview's sidebar actions (#2425)
  test: scope row menu placement checks to active section rows (#2493)
  feat(relay): detach channel windows to a point in history (#2042)
  feat(identity): decide a missing backup choice from community membership (#2378)
  Offer Builderlab decisions in FI builds (#2227)
  feat(inbox): preview recent Inbox activity when hovering the sidebar row (#2347)
  Add collapsible Solo and Team sidebar sections (#2296)
  fix(codex): open long Codex transcripts a page of items at a time (#2259)
  Keep Solo model and effort choices per chat (#2342)
  feat(sidebar): smooth channel reordering with bounded motion (#2108)
  Connect legacy agents from DMs and channel mentions (#2423)
  ...
michaelneale added a commit that referenced this pull request Nov 4, 2026
* origin/main: (31 commits)
  test: fix invitation and coordination checks on main (#2517)
  perf(search): name searched people from the profile search response (#2353)
  Use the merge queue for pull requests that require it (#2452)
  fix(browser): load scheme-less address input over https, falling back to http (#2467)
  fix(identity): stop one-click key unlinks and show the linked backed-up key (#2486)
  fix(test): observe coordination transition events (#2297)
  fix(profiles): keep the next view's profile reads through a shrink (#2395)
  fix(channels): show Add and Send message errors above the Manage members list (#2421)
  fix(threads): keep reply hooks off agent activity bubbles (#2422)
  fix(channels): offer only Copy link in a not-joined preview's sidebar actions (#2425)
  test: scope row menu placement checks to active section rows (#2493)
  feat(relay): detach channel windows to a point in history (#2042)
  feat(identity): decide a missing backup choice from community membership (#2378)
  Offer Builderlab decisions in FI builds (#2227)
  feat(inbox): preview recent Inbox activity when hovering the sidebar row (#2347)
  Add collapsible Solo and Team sidebar sections (#2296)
  fix(codex): open long Codex transcripts a page of items at a time (#2259)
  Keep Solo model and effort choices per chat (#2342)
  feat(sidebar): smooth channel reordering with bounded motion (#2108)
  Connect legacy agents from DMs and channel mentions (#2423)
  ...

Signed-off-by: Michael Neale <[email protected]>
tulsi-builder added a commit that referenced this pull request Nov 5, 2026
## Why

People said Inbox's filter bar was crowded and Archive didn't make
sense. They liked the tabs and the Unread toggle, and they wanted the
page called **Activity**, like Slack's. This PR keeps those parts and
removes the rest, so the toolbar fits on one row for as long as
possible.

## What

- **Rename:** Inbox is now **Activity**, in the page title, the sidebar
row, the shortcut titles ("Open … in Activity") and the help docs. The
bell icon stays. Plugin id `buzz.inbox`, page id `inbox` and storage
keys are unchanged.
- **Toolbar:** one left-aligned row.
- Type tabs come first: All, Threads, Mentions, DMs. The first tab lines
up with the title-bar bell.
- A text-only **From** menu (Anyone / People / Agents) follows the tabs.
It gets a subtle fill while it's narrowing the list.
- The **Unread** switch sits right after From, so it stays in view on a
full-width page.
- When the window is too narrow, Unread wraps to the next line first,
then From.
- **Archive removed:** every conversation stays in the list. The row ✓
is now **Mark as read**. It only shows on unread rows.
- With Unread on, the row leaves the list and an **Undo** toast appears.
- When nothing unread is left, the list says **All caught up** and
offers **Show all**.
- **Drafts removed:** a draft stays in the conversation where it was
written.
- **Focus:** after Mark as read, focus moves to the next row only if
focus is still in the list. If someone moves to the composer while the
read is still saving, their cursor stays where they put it.
- **Sidebar hover card (#2347):** now titled Activity, with an **Open
Activity** button. It no longer hides conversations that were archived
before, so it lists the same conversations as the page.
- **Filter routes (#2394):** the route contract drops `show` and
`drafts`. Older links that still carry them open the page with its saved
filters instead of failing: `show` is ignored, and `{ drafts: true }`
alone opens the saved filters.

## Screenshots

<!-- Default toolbar · From menu · From: People · Marked as read + Undo
· All caught up · Narrow (560px) · Hover card -->

## Not in this PR

- The Inbox badge and bold label: #2337.
- Which items count as unread: #2401.
- The unread logic from #2275.
- "Retry inbox" and "Inbox history unavailable" in
`src/features/relay/session.ts` and `inbox-feed.ts`. Those files are
shared relay code, so the strings are unchanged.
- `archive.ts` no longer has a caller in the page or the card. Removing
it is a follow-up, so this PR doesn't touch the files #2275 edited.

## Testing

- **Unit:** `vitest run src/bundled/inbox src/app src/features/pages
src/plugins` passes, 1347 tests after the latest rebase.
- New: Mark as read via the ✓ and via the context menu, each when the
save succeeds and when it fails. In all four, focus stays in the
composer the user moved to. All four fail without the fix.
- New: route cases for sender-only routes and links that still carry
`show` or `drafts`.
- **Browser, Chromium and WebKit:** `tests/browser/inbox*` and
`sidenav-polish` pass, 74 tests after the latest rebase.
- New `inbox-mark-read.spec.mjs` covers the one-row toolbar layout, bell
alignment, Mark as read with Undo, and All caught up.
- **Static checks:** `tsc`, `lint` and `design:check` are clean.
- **Agent review:** Crack Catcher's blocking finding was a focus jump
during a pending read. It's fixed, and Crack Catcher confirmed it.
- **Human testing:** tulsi tried it in the desktop dev app and confirmed
it all works.

buzz-review-completed

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

<img width="1280" height="800" alt="1 Default toolbar"
src="https://github.com/user-attachments/assets/4e3d23ac-5529-48b6-ab91-594ce3933e52"
/>
<img width="1280" height="800" alt="2 From menu open"
src="https://github.com/user-attachments/assets/7e2c86df-998a-467d-9ae8-10ea8c73bcbc"
/>
<img width="1280" height="800" alt="3 From People (dark)"
src="https://github.com/user-attachments/assets/3dd58da4-204b-46de-b4e5-cc531f7bd580"
/>
<img width="1280" height="800" alt="4 Marked as read with Undo"
src="https://github.com/user-attachments/assets/d54214a4-2990-4080-bf1d-15d6cacfd4fc"
/>
<img width="1280" height="800" alt="5 All caught up"
src="https://github.com/user-attachments/assets/215850bb-8ed3-49c4-a4a7-a7cf6e2530ff"
/>
<img width="560" height="800" alt="6 Narrow window"
src="https://github.com/user-attachments/assets/e4a1fa0c-a19e-45de-b082-43c5295f7cba"
/>
<img width="1280" height="800" alt="7 Hover card"
src="https://github.com/user-attachments/assets/63947445-59ae-4c49-97b4-d04ae12c6875"
/>

---------

Signed-off-by: tulsi <[email protected]>
Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>
tulsi-builder added a commit that referenced this pull request Nov 9, 2026
Activity moves out of the dock and back into a row at the top of the
Messages sidebar, where it was before #2626. Drafts & sent sits below it
when that plugin is on.

- **Hover preview:** resting the pointer on the row opens the same
Activity card as before (#2347). The shell already supports previews on
sidebar rows, so the card works unchanged.
- **Reaction emoji:** the row's bell swaps for the latest reaction
emoji, as the dock tile did. `ActivityNavigationIcon` is now sized for
the row's 16px icon slot, and the dock's Activity-only icon code is
gone.
- **Selection:** opening Activity keeps Messages selected in the dock
and keeps its sidebar, like other pages listed there (#2779).

The change is host policy in `pagePlacement()`
(`src/app/shell/presentation.ts`): `buzz.inbox/inbox` resolves to
`"sidebar"` whatever placement it registers, and it's dropped from the
dock order. No FOUNDATION files are touched.

Docs (`docs/inbox.md`, `docs/buzz-help.md`, `docs/shell-design.md`, the
Activity README) and Inbox code comments now say "Messages sidebar"
where they said "app dock".

## Tradeoffs

- **Fewer places to see it.** The row lives in the Messages sidebar.
With another dock app open, the sidebar collapsed (below 650px), or a
plugin narrowing the sidebar, Activity takes an extra step (Messages,
Show navigation, or Search/shortcut), and the reaction-emoji swap isn't
visible until the sidebar is.
- Activity is no longer a dock tab. This reverses that part of #2626, so
the dock's owner should sign off.

## Screenshots

Synthetic browser-fixture data only.

Hovering the Activity row, light:

![Activity hover card beside its Messages sidebar row, light
theme](https://github.com/block/buzz-app/raw/26a45ad84c2494d21fff0399adff249f76095b1d/activity-sidebar-preview-light.png)

Dark:

![Activity hover card beside its Messages sidebar row, dark
theme](https://github.com/block/buzz-app/raw/26a45ad84c2494d21fff0399adff249f76095b1d/activity-sidebar-preview-dark.png)

The row's bell swapped for a new reaction (✅):

![Activity row showing a reaction emoji in place of its
bell](https://github.com/block/buzz-app/raw/26a45ad84c2494d21fff0399adff249f76095b1d/activity-sidebar-reaction.png)

## Tests

- New `PageNavigation.test.tsx`: the sidebar Activity row swaps its bell
for the emoji and keeps its accessible name.
- `presentation.test.ts`: bundled Activity resolves to the sidebar
whatever placement it registers.
- `navigation-owner.test.ts`: Activity now belongs to Messages.
- `entity-navigation.test.tsx` also checks that Activity has no dock
tab.
- AppShell and entity-navigation tests that used Activity as an example
dock app now use Bestie, Reminders or Workflows. Entity-navigation tests
find the Activity row only after the sidebar's one startup remount, so
they never click a detached row.
- Browser specs find Activity with a shared `activityRow()` helper in
`tests/browser/navigation.mjs`. No browser cases were added or removed.
`inbox-preview.spec.mjs` now covers hovering the sidebar row; on a
selected row it checks for the row's title hint where it used to check
for the dock's tooltip. `inbox-tabs.spec.mjs` expects the 16px row emoji
instead of the 18px dock one.

Validation on this branch:
- `pnpm check` (biome, typecheck, design checks) passed.
- Full `vitest run`: 20,129 passed, 1 failed. The failure,
`MemoryPanel.test.tsx > copies a memory from its context menu`, passes
when run on its own on both this branch and main.
- Chromium browser specs (`--project chromium`) covering Activity or the
dock (41 files): after the fixes, every case touched passes. Three cases
fail identically on the base commit without this change, so they're out
of scope here: `layout.spec.mjs` "Alt+ArrowUp from a message link
preserves native scroll ownership", and both `typing.spec.mjs` "a
growing … composer lifts history" cases (React error #185).
- **Narrow windows:** below 650px the Messages sidebar is collapsed, so
getting to Activity takes Show navigation or Search. `inbox.spec.mjs`'s
390px case opens Activity through Show navigation and checks that the
drawer closes.
- After review fixes (`916e24d3e`): the vitest files touched (30 files,
689 tests) and `tsc` pass; the Chromium specs rerun with 45 passed and 1
failed, the Alt+ArrowUp case that also fails on main. `layout.spec.mjs`
now uses Workflows as its non-Messages dock destination.
- tulsi tried the running desktop app: the row, hover preview and emoji
swap all work.

Not run locally: WebKit browser projects and native builds. CI covers
those.

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

---------

Signed-off-by: tulsi <[email protected]>
Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>
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