Skip to content

NEXUS-4082: Keep the new-message boundary stable during a visit - #2083

Merged
kazemek21 merged 1 commit into
mainfrom
kylez_NEXUS-4082_channel_unread_boundary
Nov 5, 2026
Merged

kazemek21 merged 1 commit into
mainfrom
kylez_NEXUS-4082_channel_unread_boundary

Conversation

@kazemek21

@kazemek21 kazemek21 commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Keep the “New messages” divider fixed during a channel visit, even as reading advances unread state or the timeline temporarily remounts. Capture one verified entry boundary rather than deriving it from the moving unread cursor. Fixes NEXUS-4082.

How

The existing message-visit owner records loaded row IDs, verified history completeness, the read-intent revision, and read evidence before the timeline mounts. Unresolved attention stays uncertain; later synchronization cannot redefine entry. The timeline captures one unread message ID and keeps it under that visit token. It checks evidence in chronological order only through that first unread row. Messages received during loading or an inline seek cannot become the entry boundary. If the entry boundary cannot be proved, omit the divider rather than guess.

Read eligibility, focus requirements, membership checks, settled-reading dwell, and read-state writes remain with their existing owners. No new focus handoff or force-read operation is included. Normal guest-channel opening focus and Search Buzz dismissal belong to the human-owned #2140; this PR neither implements nor depends on that change.

Verification

Snapshot 8219ae795 includes the merged selector repair #2493. Blox passed root typecheck, touched-file Biome, design checks, and 830 tests across 16 complete affected files. All 58 browser cases passed in Chromium and WebKit across the complete new-message-boundary, history-loading, activity-groups, activity-lifecycle, and navigation-row-menu files, without skips or retries. All 39 selected source hashes match the uploaded snapshot and committed files; downloaded artifact digests match the receipt. The box was deleted. Current-head hosted CI is pending.

The six later-row ordering cases failed before this repair, while six earlier-row controls passed. All 12 now pass: later unresolved evidence cannot suppress an earlier proven unread boundary. Unresolved or changed evidence at or before that boundary still prevents choosing it.

The retained divider scope adds three browser scenarios (six engine cases), removing none. Browser-only coverage exercises real virtualizer geometry, keyboard scrolling, selection/copy, and signed delivery through the broker/store. The saved-offset regression previously failed with 32px drift in both engines; the original <4px tolerance is unchanged. The mounted ordering matrix stays in React Testing Library. No browser cases were added or removed by this repair.

The remount fixture toggles timeline presentation under MessageManagement; it does not drive the real ConversationTab → ChannelBody inline-seek route. That wiring was source-traced only. Native WebView and live multi-device behavior remain unproven.

Earlier opening-focus scope was withdrawn: its ten mounted cases and two width-based browser journeys (four engine cases) were removed, with ChannelsPage.tsx and channel-permissions.spec.mjs restored to base. Those cases covered the withdrawn focus behavior, not this divider. No replacement focus proof is claimed.

Historical synthetic screenshots at 993af5f (not current-head native verification).

Before the boundary.
boundary-before

After the boundary.
boundary-after

After channel revisit, with grouped messages.
boundary-grouped

Narrow, dark view with larger text.
boundary-narrow-dark

Limits

The divider stays absent for the whole visit when history was incomplete or unverified at entry, even if older rows load later. It may also be absent if entry membership or evidence at or before the first unread row cannot be proved. Read-intent changes or changed evidence needed to locate the boundary before capture conservatively omit it. Uncertainty in a later row no longer suppresses an earlier proven unread boundary. A visible read/unread transition cannot exclude older unread messages, and delayed rows cannot establish entry membership.

A read change in another channel while entry awaits evidence can also suppress the divider for this visit. Entry reconciliation can clear a prior message-force marker and advance the same global revision. The public API does not expose a target-specific entry set or mutation cause. This PR retains that conservative guard; focused tests preserve the omission and verify the affected messages remain unread.

The original live #announcements unread report remains unproved. Private screenshots did not establish its channel role or focus state and are not published. This divider-only change claims no confirmed fix for that incident. Native WebView was not exercised.

Coordination

Based on main, including the merged held-reader resize and fixture repairs. Keeps the existing unread/relay owners and does not expand their APIs. The opening-focus overlap was removed rather than creating a competing owner or an ordering dependency on #2140.

— Generated with Codex (gpt-6.1-sol) on behalf of kylez.

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch 5 times, most recently from 6f2edb5 to 8e70de2 Compare October 31, 2026 18:18
@kazemek21
kazemek21 marked this pull request as ready for review October 31, 2026 18:37
@kazemek21
kazemek21 requested a review from a team as a code owner October 31, 2026 18:37

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

🤖 Four unread-boundary capture issues: history gating, late evidence, entry reconciliation, and post-entry arrivals. See inline evidence and validation limits.

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch from 8e70de2 to 42479be Compare October 31, 2026 19:24
@kazemek21

Copy link
Copy Markdown
Contributor Author

Disposition of the four-point review at 42479be:

All four threads have attributed answers and invite reopening. Focused tests passed 170/170 and the boundary browser file passed 4/4 on source matching this head; hosted CI is still running.

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

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

Reviewed head 42479be7fb243288128be3bbee774afddda4dffc against base c253fc051e1aa7acb4c6c7f2f64f8b779e2a41cd.

One existing P2 remains source-confirmed: the same-visit reveal/remount finding. ConversationTab.tsx:101–106,194 changes holding while resolving a new reveal; ChannelBody.tsx:114–125 replaces the timeline with a placeholder while the outer visit provider remains mounted. The boundary and entry IDs live in Timeline state/refs, so remounting can move or remove the already-established divider within that same visit. Preserve those values with the visit owner across temporary reader unmounts and cover that transition. Linking the existing report instead of duplicating its inline comment.

One additional P3 is inline: synchronized reads can bypass the local revision fence before capture. This affects the entry annotation, not stored read state. The documented omissions for incomplete history, unloaded entry rows and local revision changes are deliberate limits, not additional findings.

Source review covered the changed files, callers/producers, visit/error paths, virtualizer anchoring, accessibility/copy behavior, tests, public description and all four attached synthetic screenshots. No concrete public-material disclosure found. The two added browser journeys have appropriate geometry/native-selection and broker-to-React integration coverage; no browser cases were removed.

Validation: no code execution, tests or app launches by this review. Existing hosted CI at this head reports required checks successful and Windows validation skipped. Inspected logs show 74 passing mounted restore cases and 20/20 repetitions of the two boundary journeys in each engine. Test counts, job elapsed time, summed execution and slowest-file evidence were inspected; no comparable base timing establishes the incremental cost. Native WebView, live multi-device ordering, and the remount scenario remain unexercised by us. This is a COMMENT review, not approval.

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch from 42479be to 7a47c48 Compare November 1, 2026 03:35
@kazemek21

Copy link
Copy Markdown
Contributor Author

Both findings in the source review are fixed at 7a47c48:

  • Same-visit reveal/remount: retain completed boundaries and pending baselines under the existing visit token, including a captured empty result.
  • Synchronized read ordering: detect changes in already-known entry attention without relying on the local-intent revision, and omit the uncertain divider instead of shifting it.

All five new mounted regressions failed on the prior head and pass after repair. Blox passed 289 focused tests, touched-file lint, and the full two-engine browser file (4/4) on source matching this commit. Hosted checks are still running. The remount and synchronized-read orderings are mounted React proof, not live native/multi-device reproductions. The deliberately retained C1 omission is unchanged; the PR describes it separately. Both threads now have attributed answers and invite reopening.

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

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

🤖 One test-maintenance finding; no new production correctness or security defect established in this pass.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated follow-up source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

Reviewed head 7a47c48a6c69f041f84d32ff45d5fbd631c7cd82 against base c014f54b33f6003612c3b1f2e6314354bdcb514d, focusing on changes since reviewed head 42479be7fb243288128be3bbee774afddda4dffc.

No new actionable findings. Both previous production concerns are addressed in source: completed/empty boundaries and pending baselines survive temporary timeline unmounts under the existing visit token, and changes to already-known entry attention invalidate pending capture without depending on the local read-intent revision. The five added mounted cases cover these orderings. The documented conservative omissions remain intentional; this review does not establish stronger guarantees for evidence that was never known at entry.

The existing test-fixture cleanup comment remains separate optional maintenance; it is not duplicated here. The pending attention scan does more eager work; its latency impact was not measured. No browser journeys were added or removed in this follow-up.

Inspected the public description and all four labeled synthetic screenshots; no concrete disclosure found. Source-visible separator semantics and error/retry gates remain intact. The screenshots represent 993af5f, not a fresh render of this head.

Validation: source-only; no tests, builds or app execution by this review. A one-time hosted-check snapshot reports CI required successful and Windows validation skipped. The author’s detailed test counts were not independently reproduced. Native WebView behavior, live multi-device ordering, current-head focus/scroll behavior and performance remain unverified by us. This is a COMMENT review, not approval.

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch from 7a47c48 to 7abf46d Compare November 1, 2026 04:08
@kazemek21

Copy link
Copy Markdown
Contributor Author

The test-maintenance review is addressed in 7abf46d: obsolete latest-message fixture bookkeeping and one equivalent invocation were removed, retaining no-divider/unchanged-unread assertions and the browser readiness poll. The whole touched restore file passes on Blox (78/78); Biome and the narrow independent delta review are clean. Hosted CI is pending. On-thread disposition.

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

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated follow-up source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

Reviewed head 7abf46d37cf0eb671b3e0be94558ae32559bea56 against base c014f54b33f6003612c3b1f2e6314354bdcb514d, focusing on the fixture cleanup since reviewed head 7a47c48a6c69f041f84d32ff45d5fbd631c7cd82.

No new actionable findings. The endpoint comparison changes only ChannelTimeline.restore.test.tsx (+13/-38); production and browser files are unchanged. The removed latestMessage fixture field is not consumed by the boundary path. Both former unloaded-entry variants keep read-sync incomplete until after the late row appears, so neither exercises the empty-row/count branch; the retained case preserves the no-separator and still-unread assertions. Deferred entry/snapshot ordering and fixture teardown remain intact. No browser cases were added or removed in this follow-up.

Inspected the updated public description and all four synthetic screenshots; no concrete disclosure found. The screenshots remain from 993af5f, not a new rendering of this head.

Validation: source-only; no tests, builds or app execution by this review. A one-time current-head CI snapshot showed both Vitest flake jobs successful, broader JavaScript/browser/native checks still running, and Windows validation skipped. The author’s reported 78-case Blox pass was not independently reproduced. Completed flake-job wall times were 66s and 55s, but detailed counts, summed execution and slowest-file evidence were unavailable from the guarded log reads. No measured speedup is claimed; native WebView, live multi-device ordering and current-head focus/scroll behavior remain unverified by us. This is a COMMENT review, not approval.

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch from 7abf46d to 7882e7f Compare November 1, 2026 04:56

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated follow-up source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

Reviewed head 7882e7f against base 7878f06, focusing on the actual endpoint changes since 7abf46d.

No new actionable findings. Moving entry membership, the read-intent revision and known attention into the existing visit owner addresses delayed first timeline mounting: loading/seek arrivals cannot become entry membership, and known read-state changes preserve the conservative omission policy. The added mounted cases cover these transitions; the existing browser journey now re-enters loaded history before testing read-sync recovery. No browser cases were added or removed in this follow-up.

Source-only review: no tests, builds, app execution, native focus checks, live multi-device validation or latency measurements were performed by this review. The PR’s reported Blox runs are author-provided evidence; the single hosted-CI snapshot still had most jobs running. Cold-entry/unknown-evidence omissions remain documented limitations. Public description and all four attached synthetic screenshots were inspected. This is a COMMENT review, not approval or merge authorization.

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

🤖 One merge-blocking test regression at this head; no new production or security defect established.

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch from 7882e7f to b5b7ea3 Compare November 1, 2026 05:11
@kazemek21

Copy link
Copy Markdown
Contributor Author

The fixture regression in the review is fixed in b5b7ea3. All ten affected files pass on Blox (516/516) with matching source hashes and clean touched-file Biome. Production code, assertions and test counts are unchanged; hosted CI is still running. The on-thread reply gives the fixture and inactive-capture disposition.

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

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated follow-up source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

No new actionable findings. Reviewed head 13b9e29ad227ee8cb78e5fa4c806a02f045d4ed9 against supplied base f1e40ea0e559db97f6ec6815b92d13a6a861d377 (PR merge-base 44aa6ab34833043c94bb8e78d7cceeb8d59fbe4a), separating inherited changes from the follow-up since 8abe32d0e922cfc52f5ddb7f0ab4dcffb4de8440.

The source retains provisional and unresolved entry evidence across delayed mounting and same-visit remounts, so later synchronized reads cannot substitute a newer unread boundary. Saved-boundary restoration now keeps that row mounted before measuring its divider inset. The mounted ordering cases and added warm-cache browser fixture cover these seams in source; the latter toggles timeline presentation rather than exercising the actual inline-seek route. The separate opening-focus behavior and its dedicated tests were withdrawn, as documented; the prior focus finding therefore no longer applies to this divider-only change.

Reviewed the public description and all four attached synthetic screenshots, with no concrete disclosure finding. The screenshots are from 993af5f, not a fresh render of this head. Existing loading/error/retry paths and selection-copy filtering were source-traced. Conservative omission when entry evidence cannot establish a boundary remains an intentional limit.

Validation: source-only; no tests, builds or app execution by this review, and current CI was not assessed. The author’s reported runs were not independently reproduced. Native WebView, live multi-device ordering, actual focus/scroll behavior, accessibility and latency remain unverified by us. The referenced issue’s contents were not independently retrieved; scope was assessed from the PR and repository documents. This is a COMMENT review, not approval.

@kazemek21
kazemek21 requested a review from wesbillman November 3, 2026 06:37
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch 9 times, most recently from c24a9d9 to 5817745 Compare November 4, 2026 02:52

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated follow-up source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

No new actionable findings. Reviewed head 58177457c49d90f98c3b76538ddef73e22c4da38 against supplied base cbc71c85afc89abbb5120a3a584649231c26e91d (PR merge-base 6565eea00b0a875a0b4ebf9766ce915069de44f1), focusing on changes since reviewed head 13b9e29ad227ee8cb78e5fa4c806a02f045d4ed9 and separating inherited base changes.

Entry-history completeness now remains fixed at visit entry; later paging cannot establish a boundary from incomplete entry evidence. Backdated arrivals before first timeline mounting are skipped without discarding a known entry divider. Both boundary subscriptions stop for inactive or failed visits and after capture. The mounted regression cases cover these transitions. The integration retains chronological entry scanning and combines the divider inset with contained-message restoration without adding the offset twice.

The documented omission for incomplete history or uncertain entry evidence is intentional. The existing browser boundary spec is byte-identical to the prior reviewed revision; no browser scenarios were added or removed in this follow-up. Reviewed the updated public description and all four synthetic screenshots, with no concrete disclosure finding. Those screenshots remain from 993af5f, not a fresh rendering of this head.

Source-only: no tests, builds or app execution by this review; current hosted CI was not assessed, and the author’s reported Blox runs were not independently reproduced. Native WebView, live multi-device ordering, current-head focus/scroll behavior and latency remain unverified. The referenced issue’s contents were not independently retrieved; scope was assessed from the PR and repository documents. This is a COMMENT review, not approval.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4082_channel_unread_boundary branch from 5a39bb3 to 8219ae7 Compare November 4, 2026 23:31
@kazemek21
kazemek21 requested a review from gilgtm November 4, 2026 23:50
@kazemek21
kazemek21 merged commit 3903ea1 into main Nov 5, 2026
42 checks passed
@kazemek21
kazemek21 deleted the kylez_NEXUS-4082_channel_unread_boundary branch November 5, 2026 01:13
gilgtm added a commit that referenced this pull request Nov 5, 2026
…lled-read

* origin/main: (28 commits)
  Run native Codex through plugin execution targets (#2150)
  test: split Solo access browser scenarios (#2499)
  feat(agents): let any agent change Buzz settings through the MCP (#2248)
  Let plugins decorate host sidebar rows and add sort modes (#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (#2083)
  feat(agents): move version history into settings (#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (#2465)
  feat(plugins): let agents request plugin changes for owner review (#2280)
  fix(messages): keep the hover toolbar while its menu is open (#2439)
  feat(workflows): suggest channel members for @ in Send Message text (#2309)
  AIDA-2344: Exclude muted channels from Dock (#2313)
  feat(search): add slack-style message search operators on desktop (#2463)
  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)
  ...

Signed-off-by: Gil Birman <gil@squareup.com>
Co-authored-by: Claude <noreply@anthropic.com>

# Conflicts:
#	docs/buzz-help.md
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 5, 2026
…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>
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 5, 2026
* upstream/main: (22 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>
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 5, 2026
* 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>
ljknight added a commit to ljknight/buzz-app that referenced this pull request Nov 5, 2026
…nks-in-browser

* origin/main: (138 commits)
  feat(composer): optional ⌘/Ctrl+Enter to send (block#2331)
  test: use keyboard activation for reply caret setup (block#2557)
  test: settle member search before invitation expansion (block#2532)
  test(agent-command): isolate the copied executable writer (block#2537)
  test(sidebar): wait for the rolled-back row to leave Starred before collapsing Work (block#2574)
  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)
  ...

# Conflicts:
#	src/bundled/github/README.md
damienrj added a commit to damienrj/buzz-app that referenced this pull request Nov 5, 2026
* commit 'a39dba132^':
  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)

Signed-off-by: damienrj <damien@squareup.com>
ljknight added a commit to ljknight/buzz-app that referenced this pull request Nov 5, 2026
* origin/main: (121 commits)
  feat(composer): optional ⌘/Ctrl+Enter to send (block#2331)
  test: use keyboard activation for reply caret setup (block#2557)
  test: settle member search before invitation expansion (block#2532)
  test(agent-command): isolate the copied executable writer (block#2537)
  test(sidebar): wait for the rolled-back row to leave Starred before collapsing Work (block#2574)
  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)
  ...

# Conflicts:
#	docs/buzz-help.md
kazemek21 added a commit that referenced this pull request Nov 5, 2026
…a channel (#2512)

## TL;DR

Adds `conversation.ui.Conversation`, a public host-owned scope that lets
a plugin's own `ui.Message` rows and `ui.Composer` for one channel get
native Edit, confirmed Delete with recovery and retry, and host media
review. Mounting it never marks anything read, follows a thread or
enters the channel visit. Cross-channel overview cards need this; today
a bare `ui.Message` row has neither Edit nor Delete, and `ui.Thread`
always visits.

Fixes NEXUS-4193

## How it works

`ui.Conversation({ session, scope, channelId, channelName?, threadId?,
children(interactions) })` renders the existing `ConversationChannel` in
its host-dialog, non-visiting mode (`host` plus `active={false}`). Inbox
Drafts already uses this mode internally. Inside it:

- `MessageManagement` supplies the row menu's Edit/Delete under the
existing permission checks, the delete confirmation and the
failed/unconfirmed recovery notice.
- `MessageEditScope` makes Edit target the `ui.Composer` in the same
scope. A scope without a composer offers no Edit, as in other previews.
- `ConversationHost` owns media review and panel overlays, focus return,
and the existing stale-session guard (nothing opens once the relay
snapshot is no longer this ready session).

`children` receives the host's `onOpenLink`, `canOpenLink` and
`onOpenMediaReview` to pass to the rows. `EmbeddedThread` and the new
scope now share one `HostedChannel` wrapper, so `ui.Thread` behavior is
unchanged. No changes to `MessageManagement`, `ConversationHost`,
`MessageComposer` or `contracts.ts`.

## Evidence

`src/features/conversation/EmbeddedConversation.test.tsx` mounts two
fictional channel cards (real relay session, real `ui.Message` rows and
`ui.Composer` from `ConversationService`; only `MediaReviewViewer` is
reduced, since it has its own suites). It covers:

- the same rows without the scope have no Edit/Delete; inside it, each
card offers both
- mount, background presentation, re-present and unmount never call
`enterChannel`, `leaveChannel`, `follow`, `markMessageRead`,
`markChannelRead` or the roster's `profiles.reserve`, and publish
nothing
- Edit fills only that card's composer and publishes a kind-40003 edit
tagged with that card's channel. The other card's message is untouched.
- Delete: cancel publishes nothing; confirm publishes kind 5; a relay
rejection shows the recovery notice in that card only; Retry resends the
same event id and the row goes away
- media review opens with the card's channel and message, and stops
opening after the session stops being ready

Fail-then-pass: with `active={false}` removed from the scope, all 4
tests fail (`enterChannel` and `profiles.reserve` are called). The scope
restored, all pass.

The packed `@buzz/author` consumer fixture
(`tests/fixtures/conversation-consumer`) now renders its rows and
composer inside `ui.Conversation` and passes `interactions.onOpenLink`
to its rows. So the generated declarations must export the new component
and its `children(interactions)` type, and the existing
`conversation.spec.mjs` journey mounts it in the real app. The fixture
type-checks against freshly built declarations. As a negative control, a
misspelled interactions field fails `tsc`.

Run on a Linux Blox box on base `a34ba4f` (the local host-build guard
refused under load):
- `vitest run src/features/conversation
src/features/messages/MessageManagement.test.tsx
src/bundled/inbox/DraftsView.test.tsx`: 19 files, 221 tests passed
- `tsc --noEmit` and `biome check --error-on-warnings` on the touched
files: clean
- `pnpm author:build`, packed and installed into a copy of the consumer
fixture, then `pnpm build` (`tsc`): clean

No browser journeys were added or removed. The existing consumer journey
now mounts the scope; it runs only in hosted CI, not locally. Focus,
overlay and native-edit geometry are unchanged existing
`ConversationHost`/composer behavior that existing suites already cover.

## Limits and coordination

- Not exercised in the native desktop app or a browser journey. Hosted
CI runs the full suites.
- Two `ui.Conversation`s for the same channel each show that channel's
recovery notice, because recovery is per channel. The docs say to render
one per channel or card.
- No overlap in hunks or behavior with #2483 (Solo list arrangement/row
action in `service.tsx`), #1889, #2306 or #2303 (`contracts.ts`), #2083
(`MessageManagement.tsx`) or #2167 (`ConversationHost.tsx`). This PR
does not edit `contracts.ts`, `MessageManagement.tsx` or
`ConversationHost.tsx`.
- This PR doesn't adopt the scope in any plugin. That is separate work.

## Initiative context

[Buzz Threads
status](https://g2.sqprod.co/apps/kylez-buzz-threads-status)

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

---------

Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
matt2e added a commit that referenced this pull request Nov 10, 2026
…3071)

## Problem

The "New messages" divider from #2083 never appeared in a channel with
more than 20 top-level messages, which is most channels anyone is behind
in. The channel window loads 20 rows on entry, so such a channel always
enters with `hasMore`, and the entry boundary refused to settle while
history was incomplete. The guard existed for a real reason: with
unloaded rows above the window, the first unread *loaded* row need not
be the first unread row.

**Repro** (fixture relay, `readState: true`, `historyCounts: { alpha:
25, beta: 15 }`):

1. Open #Alpha (25 messages), stay at the bottom for a second so the
catch-up read saves.
2. Open #Beta (15 messages) and stay at the bottom.
3. From another account, post three messages to #Alpha, then reopen it:
three new rows, **no line**.
4. Post three messages to #Beta, reopen it: the line sits above the
first new row.

Same read state, same three arrivals; only the history length differs.

## Fix

- **`oldestUnreadId` on the channel snapshot**
(`src/features/relay/unread.ts`): names the oldest unread message the
timeline shows (a top-level post or broadcast reply, in rendered order).
It ignores own messages, quiet agent rows and non-broadcast replies,
follows reads, and clears when nothing is unread. It comes from the same
bounded evidence as `observedCount`. `equal()` compares it so
subscribers see it move.
- **Timeline check** (`src/features/messages/ChannelTimeline.tsx`): when
entry history is incomplete, the boundary settles with no line unless
`oldestUnreadId` is a row loaded on entry. Complete-history visits still
place the line at the first unread entry row, but no longer wait on
`hasMore` or `historyLimited`, so a window that gains `hasMore` after
entry (live arrivals trimming it) settles from the entry rows instead of
staying pending. The pending boundary filters rows to the entry set
before classifying them. The line still stays put for the visit as older
pages load, rows arrive and the reader scrolls. `MessageManagement.tsx`
is untouched; `entryHistoryComplete` means what it meant.
- **`sampled` wait**: a new snapshot flag saying a repair has read every
channel in unread scope since evidence was last emptied (cache clear or
capacity overflow) or a channel entered that scope. Later repairs
(reconnect, backstop, refresh) only add evidence and leave it set, so
they publish no extra snapshots. Incomplete-history visits wait for it
before settling. Without it, a Chromium probe that held the repair read
and re-entered a 25-message channel drew the line at message 5 while
messages 0–4 were unread too.
- **Read-row guard**: with incomplete history, the line also needs a
read entry row above the first unread one. This covers a repair batch
that filled its 500-row limit and stopped inside the window, leaving the
first loaded row the oldest unread message known. Catch-up's `activity:`
mark reads all ordinary rows up to a timestamp, so a read ordinary row
vouches for the rows above it.
- **Cache clear now restarts the repair**: `clear()` used to leave a
connected session without a repair until the 15-minute backstop or a
reconnect; it now starts one at once, as a newly accessible channel
does. An overflow still waits for the next recovery tick.
- **Help and docs**: `docs/buzz-help.md` describes the rule instead of
the old limitation; `docs/unread.md` documents both fields with the
bounded-evidence caveat.

## Known limits

- The line can appear a moment after entry, once the repair finishes.
Until then the channel holds its own reading (dwell and bottom
catch-up), so entering does not erase the evidence its divider waits
for; the timeline asks the store for the repair itself, so a host
without the shell's startup request still gets one.
- If that repair fails or evidence overflows (freshness `stale`,
including a stale reconnect), the hold is released and reading resumes
with no divider for that visit.
- Rows dwelt on during the hold and left before it lifts are not marked
read by that dwell; the next dwell reads them. Catch-up at the bottom
still reads the ordinary rows once the hold lifts.
- No line when the unread messages begin exactly at the first loaded row
(the read-row guard has nothing to vouch for them).
- Mentions, broadcasts, broadcast replies and messages marked unread
above the window are read only by their own `msg:`/`thread:` marks,
never by `activity:`. A repair sample that stopped short can still hide
one of them, and the line then lands below it. Recorded in
`docs/unread.md`.
- The flipped pinned test variant depends on mount timing: a remote read
that leaves a loaded row the oldest unread places the line there only if
it lands before the timeline is presented. The same read after the
boundary has settled leaves no line, because the boundary is captured
once per visit.

## Reviewer notes

- **FOUNDATION**: `UnreadSnapshot` is re-exported through the FOUNDATION
type entry `src/plugins/author.ts`. This adds two optional fields in
`unread.ts` and does not edit `author.ts`, the same shape as #2998's
`row` field. Named here so the rule is applied knowingly.
- **Conflicts with #2401**, which rewrites `compute()` in `unread.ts`.
The `oldestUnreadId` hunks will conflict with whichever lands second;
this one is about 50 lines to re-apply. #2049 moves the divider JSX and
#2652 touches a nearby effect; neither overlaps the boundary logic.
- **Side effect**: because a cache clear now restarts the repair at
once, sidebar unread counts and Inbox mentions return immediately after
a clear instead of after the backstop. Three startup tests that pinned
"nothing is read after a clear" now expect the restart; an Inbox test
that expected activity to stay hidden after a clear now checks it is not
reported as nothing-to-read before the repair returns.
- The flipped variant of the pinned "outside the entry window" test is a
deliberate product call (see Known limits). A reviewer who wants the
stricter #2083 reading can keep the third variant and drop the new case,
at the cost of no line in that race.

## Testing

| Check | Result |
|---|---|
| `tsc --noEmit`, Biome on changed files | clean |
| Four related Vitest files (`ChannelTimeline.restore`, `unread`,
`MessageManagement`, `ChannelTimeline`) | 479 passed |
| Relay, messages, channel-navigation, bundled/channels suites | 7,706
passed |
| Full Vitest | 1,034 files passed |
| `tests/browser/new-message-boundary.spec.mjs` | 4/4 Chromium, 4/4
WebKit, 20/20 repeated |
| Mutation checks | removing each guard, the restart or the reset breaks
at least one test |

The new browser case seeds 25 messages, reads to the bottom, receives
three from a peer and asserts the line sits above the first; it fails on
main and passes here. New restore cases place the line inside incomplete
history (presented and late mount, stable while older rows page in) and
omit it at a first loaded row with no read row before it.
`unread.test.ts` and `unread-startup.test.ts` cover `oldestUnreadId` and
`sampled` through refresh, clear, overflow and a channel entering scope
mid-repair.

**Not run**: the native app via `just desktop`, the notification and
Inbox DM entry paths, and the restart after a clear against a real
relay. The final amend changed only tests and the publish order of one
reset, so the browser spec and wider suites were not re-run after it.

## Before and after

Same Playwright flow against the fixture relay (`readState: true`,
`historyCounts: { alpha: 25, beta: 15 }`) on main `a905c4865` (left) and
this branch (right). Around 8–13 s #Alpha (25 messages) is reopened
after three peer messages: the left has nothing above "Later message 1",
the right shows the "New messages" line between message 24 and "Later
message 1". From about 15 s #Beta (15 messages) shows the line on both
sides.


https://github.com/user-attachments/assets/07f643fa-65b6-47f0-9012-44e37901a27c

| Before (main `a905c4865`) | After |
|---|---|
| ![Before: #Alpha reopened after three new messages, no line above
"Later message
1"](https://github.com/user-attachments/assets/1e6e06d8-378a-4c36-8716-488148b1f289)
| ![After: the "New messages" line sits between message 24 and "Later
message
1"](https://github.com/user-attachments/assets/476cc5f6-57a0-4571-8d9b-8f8bac1e1d52)
|

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

---------

Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.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.

4 participants