## Summary
First-time project users can now create a project directly from the
empty state instead of reaching a dead end. The right-side project
context also uses the intended Activity label and aligns its first row
with the channel header.
The populated and empty project views share one creation flow, so
navigation, success feedback, and compatibility warnings remain
consistent.
### Related issue
Related: #6939
### Testing
- Desktop check, TypeScript typecheck, and repository file-size gate
- 10 focused project overview context unit tests
- E2E-mode build and 3 focused Playwright scenarios covering empty-state
creation, right-sidebar alignment, and Activity context
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
Project channels previously appeared as ordinary channels until the
community-wide project enumeration completed, delaying the contextual
right rail most noticeably on larger relays.
- Restore the last fully validated, relay-and-identity-scoped project
collection immediately while keeping live relay data authoritative.
- Resolve the active channel's project home through scoped
`#buzz-channel` queries instead of waiting for the complete project
scan.
- Keep snapshots aligned with community removal and relay reconnect
invalidation.
### Related issue
None found.
### Testing
- Pre-push `file-size-check`, `desktop-check`, `desktop-typecheck`, and
`desktop-test`
- Project enumeration, snapshot persistence, and relay invalidation unit
coverage
- Targeted Playwright scenario: project sidebar rows open the home
channel and nest extra channels
No numeric startup benchmark was captured; this draft validates the
cache and scoped-query behavior while leaving timing measurement for
review.
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
- inject bounded project-home identity and repository context into
managed agent sessions
- add project-aware CLI flows for creating projects, repositories,
issues, and related channels
- match project homes through the existing relay query surface, then
filter channel metadata client-side
This is Part 1 of the channel-first Projects stack. Part 2 contains
project creation and model foundations.
## Testing
- `cargo fmt --all -- --check`
- `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings`
- `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test
ignored
- full pre-push gate passed
## Post-Deploy Monitoring & Validation
- validate project-home agent context and project-aware CLI commands
against a staging relay
- healthy signals: project context matches the active channel, explicit
repo coordinates remain stable, and normal channels receive no project
block
- failure signals: cross-channel project context, duplicate project
creation, or commands targeting an unrelated repository; mitigate by
reverting this PR
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Long threads in the desktop app sometimes never load (stuck on a
skeleton until you close and reopen the panel), and a failed load
silently renders as "No replies in this branch yet" — presenting a
broken fetch as an authoritative empty thread with no way to recover.
This fixes both, the two IMPORTANT findings from the thread-load
investigation.
## Defect 1 — unbounded `/query` request
The shared `reqwest::Client` in `relay.rs` sets no timeout, and neither
`/query` request builder set a per-request `.timeout(...)`. A stalled or
half-open connection (headers or body never arrive) leaves the request
pending forever, so a thread-history load hangs on the skeleton
indefinitely.
Fix: a 30s per-request deadline on both `/query` builders, funnelled
through one `send_query_request` helper so the timeout can never be
applied to one builder and dropped from the other. Scoped per-request
rather than client-level because the same client also serves STT/TTS
model downloads, builderlab auth, and the media proxy — a client-level
timeout would cut those off. The deadline sits above the 25s WS
`HISTORY_TIMEOUT_MS` so a slow-but-live relay isn't cut off before the
WebSocket path would be. A timeout surfaces through
`classify_request_error` as the stable `"relay unreachable: request
timed out"` string.
`send()` resolves as soon as response headers arrive, so a relay that
returns headers and then stalls the body trips the deadline during body
consumption, not at `send()` — and that consumption happens on two
paths: `parse_json_response` for 2xx, and `relay_error_message` for a
non-success status (500/429/…). Both paths route their body-consumption
error through one shared `classify_body_timeout` helper so they can't
drift: a stalled body surfaces the stable `"relay unreachable: request
timed out"` string on either path rather than the malformed-response
bucket (2xx) or a bare `"relay returned 500"` status label (non-2xx). A
genuinely non-stalled error still keeps its status classification.
## Defect 2 — terminal error painted as empty
`ChannelScreen` consumed only `isPending`/`data` from the thread-replies
query. Once React Query exhausted its one retry, `isPending` was false
and the zero-length data fell through `selectDeferredListRenderState` to
the `"empty"` state — indistinguishable from a genuinely empty branch,
with no retry affordance.
Fix: plumb `isError` + `refetch` through `ChannelScreen` → `ChannelPane`
→ `MessageThreadPanel`. A pure `selectThreadRepliesSurface` helper
decides the paint in strict precedence — the load-bearing invariant is
that a terminal error **never** resolves to `"empty"`, and cached
replies stay visible non-destructively under a later error (the error
card only surfaces when there is nothing to show). The panel renders an
explicit "Couldn't load replies" + Retry card (testids
`message-thread-replies-error` / `message-thread-replies-retry`).
`ProjectConversationPanel` is a second producer of the same shared panel
and used to hard-code `threadRepliesPending={false}` with no
error/retry, so a failed load in a Projects conversation still painted
the false-empty. It now propagates the same
`isPending`/`isError`/`refetch` from its `useThreadReplies` query.
The multi-root `useThreadRepliesForRoots` hook (the Huddle transcript
and Projects-agent conversation surfaces) had the same gap in its
`useQueries` `combine`: it returned only `{ events, isPending }`, so a
failed reply subtree contributed zero rows and vanished. The combine is
now a pure, unit-testable `combineThreadRepliesResults` that exposes
aggregate `isError`/`error` plus a `refetch` that re-runs only the
failed subtrees. Both multi-root consumers render the shared "Couldn't
load replies" + Retry card when a subtree fails: the Projects-agent
conversation after its transcript, and the Huddle transcript as a
non-destructive banner above the timeline. `useHuddleChannelMessages`
used to read only `.events` and discard the aggregate state, so one
summarized root failing left the flattened transcript presenting as
complete; it now propagates `threadRepliesError`/`onRetryThreadReplies`
through `ChannelScreen` into `ChannelPane`, where successful rows stay
visible and `onRetry` re-runs only the failed subtrees.
The error card carries `role="alert"` so its asynchronous appearance is
announced to assistive tech — without a live region a screen-reader user
parked in the composer never learns the load failed or that Retry became
available.
## Tests
- `stalled_query_request_times_out_with_classified_error` — a loopback
server that never responds; asserts the stable classified timeout
string.
- `stalled_response_body_times_out_with_classified_error` — a loopback
server that writes valid 2xx JSON headers then stalls the body past the
deadline; asserts the classified timeout string, not the malformed
bucket.
- `stalled_error_response_body_times_out_with_classified_error` — a
loopback that writes `500` headers promising a body it never sends;
asserts the classified timeout string rather than the `500` status
label.
- `non_stalled_error_response_yields_status_message` — a promptly-served
`500` still surfaces `"relay returned 500 Internal Server Error"`,
pinning that timeout preservation is scoped to actual timeouts.
- `selectThreadRepliesSurface` — pending→skeleton, terminal error→error
(never empty), page-2 failure never empty, cached rows stay visible
under error, successful-empty→empty, retry-success→list,
streaming→pending, and huddle-transcript collapse.
- `MessageThreadReplyState` mounted test — terminal error renders the
error card (asserting `role="alert"`), never the empty card.
- `combineThreadRepliesResults` — multi-root aggregation/order, a failed
subtree surfaces the aggregate error and never drops rows, aggregate
pending, refetch re-runs only failed queries, all-success yields no
error.
- `thread-load-failure.spec.ts` (smoke E2E) — binds the real
channel-thread panel wiring: forces a terminal `get_thread_replies`
failure at the IPC boundary, asserts the error card renders (never the
false-empty) and Retry recovers.
- `project-conversation-load-failure.spec.ts` (smoke E2E) — the same
guard for the Projects conversation producer, driven through the
Projects Channels-tab row.
- `huddle-thread-load-failure.spec.ts` (smoke E2E) — the consumer-level
guard the combine unit test can't provide: drives the real Huddle wiring
(`useHuddleChannelMessages` → `ChannelScreen` → `ChannelPane`) with two
summarized roots, fails one subtree's fetch at the IPC boundary, asserts
the surviving root's reply stays visible while the retry alert surfaces,
then Retry recovers the failed subtree and clears the alert.
## Structure
To stay under the desktop file-size ratchet, `relay.rs`'s inline test
module moved to `relay/tests.rs`, and two pure pieces were extracted
from the panel: the empty/error reply cards (`MessageThreadReplyState`)
and the per-row branch-highlight derivation
(`selectThreadRowHighlight`).
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Diagnostic profiling on a large community (101 issues, 258 PRs) showed
Projects tab switches taking 2.5–3.6s, dominated by single React commits
of 0.5–1.5s and per-render recomputation — fetch work was already off
the main thread; the cost was building the UI.
### Measured: tab click → painted, per tab
| tab | before | after |
|---|---|---:|
| projects | 3,608ms | 320–580ms |
| repositories | 3,126–3,534ms | 310–410ms |
| tasks | 395–1,101ms | ~115ms |
| reviews | 322–2,603ms | ~96ms |
| activity | 597–741ms | ~148ms |
Single-commit ceiling dropped from 1,541ms to ≤200ms (growth steps
25–40ms). Fixes in profiled-cost order:
- **Profile popover body mounts only while open.** `UserProfilePopover`
carried seven query subscriptions plus interaction hooks per instance
even when closed; grids mount hundreds (five per card in people stacks,
one per row author) — measured **~40ms per card**, the dominant share of
the 1.2s card-tab commits. The always-mounted shell is now just the
Radix root + trigger; trigger markup, hover timing, and keyboard
handling are unchanged, and hover/tooltip event continuity is preserved
because the trigger never remounts.
- **Incremental row mounting.** The first 12 cards / 30 rows render in
the first commit; the rest stream in 36–60-per-frame low-priority
transitions. Grouped lists trim across group boundaries via a pure,
tested slicer; the mounted count survives in-place refetches.
- **Activity feed**: was rebuilt unmemoized on every render,
markdown-flattening every issue/PR/comment body in the community just to
sort and keep 30 items (~360+ flattens per render on the measured
community). Now memoized, and bodies stay raw until after the sort+slice
— 30 flattens, once per data change.
- **Contribution graph** (always-visible rail, so every tab paid for
it): ~180 day cells each wrapped in a Radix tooltip with per-cell Intl
date formatting per render. Now memoized, cells precomputed once per
data change, native `title` tooltips. (The activity-bar segments keep
their styled Radix tooltips — pinned by an existing spec.)
- **Rows/cards memoized with identity-stable props**: per-row selection
arrays were rebuilt per row per render (O(n²) — 258 PRs × 258-item
arrays each render) and are now hoisted and shared; people arrays derive
inside the memoized cards; the rail's stat walk over every issue/PR is
memoized.
- **`content-visibility: auto`** on cards and rows so offscreen entries
skip layout and paint; **tab switches run in a React transition** so the
click stays responsive while the new tree mounts.
Remaining known cost (out of scope): cold-entry data readiness — the
work-item and activity queries ship thousands of events to compute
counts (2–4s on a large community; see the fan-lifecycle PR). The
structural fix is a relay-side aggregate; tracked as follow-up.
---------
Signed-off-by: Max Lampert <maxwell@squareup.com>
**Category:** improvement
**User Impact:** Buzz-native project, repository, issue, and pull
request links now appear once as compact inline chips, with their
details available on hover.
**Problem:** Buzz-native entity links rendered both an inline chip and a
standalone preview card, repeating the same metadata and adding visual
noise to conversations. **Solution:** Exclude Buzz-native links from the
shared standalone-preview extractor while leaving entity parsing intact
for chip tooltips and preserving external web previews and attachment
cards.
<details>
<summary>File changes</summary>
**desktop/src/shared/lib/linkPreview.ts**
Stops Buzz-native preview candidates after parsing, including same-relay
git clone URLs that normalize to repository entities, while allowing
external URLs through the existing snapshot path.
**desktop/src/shared/lib/linkPreview.test.mjs**
Covers project, repository, issue, pull request, markdown-labeled,
same-relay clone, and mixed external-link extraction behavior.
**desktop/src/shared/ui/markdown/useMessageLinkPreviews.test.mjs**
Confirms sent messages no longer merge a standalone Buzz entity card
while external sender snapshots still render.
</details>
## Reproduction steps
1. Open a desktop channel containing a `buzz://project`, `buzz://repo`,
`buzz://issue`, or `buzz://pr` link.
2. Confirm the link renders as an inline entity chip without a second
standalone Buzz card below the message.
3. Hover the chip and confirm its entity metadata remains available.
4. Post an external HTTPS link and confirm its web preview still
renders.
5. Paste a same-relay `/git/<owner>/<repo>` clone URL and confirm it
uses the repository chip without a duplicate card.
## Screenshots
| Before | After |
| --- | --- |
| Inline chip plus redundant standalone Project card | Inline chip is
now the sole presentation |
| 
| 
|
**After — rich metadata stays available on hover**

## Verification
At commit `3fa74cdd342ac1f6721b7d56a7f111af31e0e6e9`:
- focused link-preview + Markdown unit suites — 119/119 passed
- targeted registered smoke E2E — 8/8 passed, including labeled
same-relay clone metadata, ordinary-link presentation, and in-app
navigation
- `cd desktop && pnpm exec tsc --noEmit` — passed
- `git diff --check origin/main...HEAD` — passed
- pre-push hooks — desktop check, TypeScript, and full desktop unit
suite passed
---------
Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz>
Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz>
Entering Projects fires a large fan: an exhaustive paginated relay
enumeration (projects/repos/tombstones), five 2,000-event work-item
queries plus assignment-operation scans, per-repo activity summaries,
and a local-repository filesystem scan. Measured on a large community
(101 issues / 258 PRs):
| query | measured cost |
|---|---|
| work-items (5 × 2,000-event REQs + assignment scans) | 3.5–3.9s |
| activity summaries | 4.1s |
| repository activity | 1.0–2.2s |
| local repository scan | 1.7s |
Two lifecycle bugs made the fan far more expensive than it needs to be:
- **Freshness windows guaranteed a full refetch on nearly every
re-entry** (60s enumeration, 30s work-items/activity, 10s local scan) —
i.e., the costs above were re-paid on almost every visit. Every local
write path already invalidates its keys explicitly (issue/PR mutations,
project creation, repo sync), so the short windows only served
remote-actor freshness. Raised to 5m/2m/2m with a 30m enumeration cache:
re-entries now paint from cache, and the fan re-runs at most every 2–5
minutes.
- **Leaving Projects left the whole fan running**, competing with the
next surface's channel fetches on the same relay connection. AbortSignal
is now threaded through the enumeration and assignment pagination loops
(optional params — behavior identical without a signal), and leaving the
surface cancels the work-items query. Deliberately NOT cancelled: the
enumeration (the always-mounted sidebar projects section observes it and
its 30m cache is valuable), repo snapshots and local scans (native work
that can't abort — cancelling would discard the finished result and
force the same clones again), and activity summaries (a single bounded
request).
Abort behavior is covered by red-first unit tests on both pagination
loops. Remaining follow-up (out of scope): the queries themselves want a
relay-side aggregate instead of shipping thousands of events to compute
counts client-side.
---------
Signed-off-by: Max Lampert <maxwell@squareup.com>
## Summary
After #6396, Projects still split chrome across the workspace header, a
copy-link control, and a labeled Actions group that mixed people,
create, and metadata. This PR finishes that surface: the right-hand
context box is unlabeled actions plus a Details group, people stacks and
contribution heatmaps are gone from that box, Create review sits with
Create task, and the top chrome is terminal / chat / info with no
copy-link. Sent project context collapses to a pill, and review file
diffs keep the last good git view instead of flashing empty while
queries refetch.
This also lands the remaining navigation polish that followed Part 3:
overview and list presentation, readme and commit layout, and opening
the latest matching conversation from the Channels tab without leaving
the project.
### Related issue
N/A. Related: #6396
## Testing
- Walked Files, Tasks, Reviews, task/review detail, overview tabs, and
chrome chat vs info in the running desktop app
- Pre-push: desktop typecheck, unit tests, Tauri checks, and file-size
gate passed
- Updated Projects smoke specs for the new context groups, Create
review, chrome order, and removed copy-link control
- Merged current `origin/main`; one conflict in discussion-channel rows
kept conversation-panel navigation and took main's bounded channel-name
lookup
## Post-Deploy Monitoring & Validation
- validate Projects workspace chrome, context box, and review file diffs
in the first staging Desktop session
- healthy signals: context box shows unlabeled actions then Details,
chat toggle sits between terminal and info, review diffs stay populated
across selection changes
- failure signals: missing Create review, restored heatmap/people in the
context box, or empty Files Changed while the review is still selected;
mitigate by reverting this PR
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
**Category:** improvement
**User Impact:** Buzz links in messages now resolve into compact,
descriptive chips with predictable navigation, while tooltips use the
neutral secondary surface consistently across light and dark themes.
**Problem:** Raw Buzz-native links and opaque identifiers make message
context difficult to scan, while oversized previews disrupt the
conversation. Missing, delayed, or deleted metadata also needs a stable
presentation that does not mislead users or break navigation.
**Solution:** Keep message and issue chips metadata-independent: message
chips render only the channel name, while issue chips render only the
repository name—never fetched content/title or an event hash. Fetched
context remains available in each hover tooltip. Other native links
continue to resolve into concise inline labels, with muted secondary
fallbacks, distinct deleted states, and existing navigation paths
preserved. This also makes two explicit shared-tooltip behavior changes:
1. **Color:** shared and rich tooltips move from the primary color pair
to the neutral secondary semantic pair, including dedicated huddle-token
handling and matching rich-descendant foregrounds.
2. **Hover behavior:** tooltip roots set Radix
`disableHoverableContent`, and tooltip content is pointer-transparent,
so moving off the trigger dismisses the tooltip instead of letting the
pointer enter and keep the popover open.
Together these preserve readable contrast, authoritative project
context, and predictable hover dismissal.
<details>
<summary>File changes</summary>
**desktop/src/features/communities/useCommunityInit.ts**
Primes channel metadata needed to resolve native links promptly.
**desktop/src/features/messages/lib/messageLinkMetadata.test.mjs**
Covers message preview extraction, fallbacks, and deleted-message
semantics.
**desktop/src/features/messages/lib/messageLinkMetadata.ts**
Normalizes fetched message metadata into compact labels and status-aware
presentation.
**desktop/src/features/messages/ui/SentFromThreadLine.tsx**
Provides thread context to linked-message metadata resolution.
**desktop/src/shared/lib/useResolvedLinkPreviews.ts**
Exposes resolved preview state for native entity chips.
**desktop/src/shared/styles/globals/markdown.css**
Adds compact chip layout and semantic disabled-state styling.
**desktop/src/shared/styles/globals/theme.css**
Defines the semantic colors used by unavailable and deleted chips.
**desktop/src/shared/ui/markdown.test.mjs**
Exercises native-link rendering and provider-backed entity metadata
behavior.
**desktop/src/shared/ui/markdown.tsx**
Routes native markdown links through metadata-aware chip components
without changing navigation contracts.
**desktop/src/shared/ui/markdown/ChannelDeepLink.test.mjs**
Covers resolved, unresolved, private, archived, and forum channel
presentations.
**desktop/src/shared/ui/markdown/ChannelDeepLink.tsx**
Renders channel names and concise channel metadata inline with stable
fallbacks.
**desktop/src/shared/ui/markdown/MessageLinkPill.tsx**
Renders loading, ready, unavailable, DM, forum, and deleted message
states compactly.
**desktop/src/shared/ui/markdown/entityLinks.tsx**
Enriches repository, issue, and pull-request chips and uses
containing-project context in tooltips.
**desktop/src/shared/ui/markdown/types.ts**
Extends markdown runtime metadata required by native chip rendering.
**desktop/src/shared/ui/markdown/useMessageLinkMetadata.ts**
Fetches and classifies linked-message metadata while preserving
navigable fallbacks.
**desktop/src/testing/e2eBridge.ts**
Adds deterministic metadata controls for runtime visual and navigation
tests.
**desktop/tailwind.config.js**
Makes semantic disabled colors available to chip styles.
**desktop/tests/e2e/entity-link-recipient-cards.spec.ts**
Verifies entity labels, project-aware tooltips, delayed metadata, and
navigation behavior.
**desktop/tests/e2e/navigation.spec.ts**
Verifies channel and message chip states, metadata resolution, deletion
handling, and navigation.
**desktop/src/shared/ui/tooltip.tsx**
Migrates the shared tooltip surface from the primary color pair to the
semantic secondary pair. It also sets Radix `disableHoverableContent`
and makes tooltip content pointer-transparent so leaving the trigger
dismisses the tooltip predictably.
**desktop/src/features/agents/ui/RestartDiffBadge.tsx**
Aligns restart-diff tooltip values and supporting copy with the
secondary tooltip foreground.
**desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx**
Aligns rich team tooltip descriptions, persona chips, avatars, and names
with secondary tooltip semantics.
**desktop/src/features/projects/ui/ProjectAuthorIdentity.tsx**
Uses the matching secondary tooltip foreground for project author
metadata.
**desktop/src/features/projects/ui/ProjectCards.tsx**
Aligns repository-unavailable tooltip descriptions with the secondary
tooltip foreground.
**desktop/src/shared/styles/globals/utilities.css**
Makes huddle tooltips consume their dedicated palette directly without
misleading primary or secondary aliases.
**desktop/src/shared/styles/globals/tooltipSemantics.test.mjs**
Pins direct huddle-token consumption and rejects semantic alias drift.
**desktop/tests/e2e/tooltip-semantics.spec.ts**
Verifies simple and rich tooltip surfaces and descendant tokens in Buzz
light and Catppuccin Mocha dark themes.
**desktop/playwright.config.ts**
Registers tooltip semantic coverage in the desktop smoke suite.
</details>
## Reproduction steps
1. Run the desktop app and open a message containing Buzz links for a
channel, message, repository, issue, and pull request.
2. Confirm message links show only the channel name and issue links show
only the repository name inline, with no fetched content/title or event
hash; confirm other native links remain compact rather than rendering
raw identifiers or expanded previews.
3. Hover the chips and confirm the neutral secondary tooltip adds
concise context without duplicating the visible label.
4. Open each chip and confirm it follows the same channel, message,
repository, issue, or pull-request navigation as before.
5. Inspect links while metadata is loading, unavailable, or definitively
deleted and confirm the fallback remains compact, legible, and navigable
where appropriate.
6. In Buzz light and Catppuccin Mocha dark themes, compare a simple
channel-members tooltip and the rich Add agents team tooltip; both
should use secondary semantics with readable nested content.
7. Move the pointer from a trigger toward its tooltip; confirm the
tooltip dismisses rather than remaining open as hoverable content.
## Screenshots
The matrix below covers both the default light theme and Catppuccin
Mocha with an orange accent. Message/issue-chip rows affected by the
stable-label pivot are temporarily omitted until they can be recaptured
from the real built UI; the prior images showed the obsolete expanding
inline label.
| State | Default light | Dark · orange accent |
|---|---|---|
| Public stream channel tooltip | 
| 
|
| Private forum channel tooltip | 
| 
|
| Public forum channel tooltip | 
| 
|
| Channel references, including unresolved | 
| 
|
| Unknown channel permalink | 
| 
|
| Archived channel tooltip | 
| 
|
| Repository entity | 
| 
|
| Pull request entity | 
| 
|
| Entity metadata fallback | 
| 
|
### Tooltip semantic surface spot check
| Buzz light | Catppuccin Mocha dark |
|---|---|
| 
| 
|
---------
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
## Summary
- give inline project agents bounded visible-page and selection context
while reusing the shared message-thread presentation
- add contextual collaboration actions for discussing project entities
in related channels
- align project list metadata, context rails, and work-item
communication actions with the active workspace
This is Part 3 of the Projects v6 stack, following #6368. Part 4
contains the remaining navigation and detail-page polish.
## Testing
- Desktop unit suite: 5,125/5,125 passed
- Projects smoke specs: 62/62 passed
- TypeScript, Biome, typography, pubkey, and differential file-size
checks passed
- full pre-push gate passed
## Post-Deploy Monitoring & Validation
- validate Projects overview/detail agent chat and discuss-in-channel
journeys in the first staging Desktop session
- healthy signals: context matches the active project/repository/work
item, messages remain in the chosen channel, and restored conversations
exclude unrelated DM history
- failure signals: stale or cross-project context, duplicate/missing
thread rows, or collaboration actions targeting the wrong channel;
mitigate by reverting this PR
Related: #6335
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
Projects workspaces now support consistent selection across projects,
repositories, reviews, tasks, and channel results. A user can collect
related entities, see the active selection without losing their current
workspace, and pass a bounded, injection-safe representation to an agent
for discussion.
Detail views now keep repository source and availability explicit while
preserving contextual actions across local and remote workspaces.
Missing or inaccessible repositories produce a recoverable state instead
of leaving the workspace ambiguous.
This is part 2 of the Projects v6 stack. Parts 3 and 4 will add
context-aware collaboration and navigation/detail-page polish.
### Related issue
Related: #6335
### Testing
- Full pre-push gate passed: file-size checks, Biome, TypeScript,
Desktop unit tests, Tauri checks, and Rust tests
- Project context safety suite: 9/9 passed, including bounded untrusted
overview data and hidden-context stripping
- Updated Playwright coverage exercises grouped selection,
selection-aware agent context, repository source states, and detail
actions
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
Part 1 of 4 stacked PRs continuing the Projects work from #6003.
- The Projects overview now follows the selected section: the right-hand
context pod stays visible across the Projects / Repositories / Reviews /
Tasks / Channels tabs and shows section-specific people, stats, and
contribution graphs, so it reads as live context rather than a detached
summary.
- Entity list rows are consolidated onto one compact line (title first,
icons after, aligned dates/counts), shared across projects,
repositories, PRs, issues, and channel lists via `ProjectEntityListRow`.
- New `projectRelatedChannels` helper resolves the channels a project is
discussed in for the overview.
Follow-ups in this stack: part 2 (selectable workspaces), part 3
(context-aware collaboration), part 4 (navigation & detail-page polish).
## Test plan
- [x] Desktop unit tests (`pnpm test`) — pass
- [x] TypeScript (`tsc --noEmit`), Biome, clippy — clean via pre-push
gate
- [x] Playwright specs updated for the new overview behavior
(`projects-v3-screenshots.spec.ts`, `sidebar.spec.ts`) — pass locally in
the smoke project
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
Large repositories were silently limited to the first 250
lexicographically sorted paths, which could leave the Files tab showing
only dot-directories. Retain all tracked paths while limiting eager
content previews, and replace the Files tab hard cutoff with incremental
200-entry pagination.
### Related issue
Fixes#4960. No duplicate PR found.
### Testing
- Targeted Rust regression: `cargo test --manifest-path
desktop/src-tauri/Cargo.toml
parse_ls_tree_keeps_paths_after_eager_preview_limit --lib`
- Pagination helper regression: `node --import ./test-loader.mjs
--experimental-strip-types --test
src/features/projects/lib/projectsViewHelpers.test.mjs`
- Desktop Biome, type checking, and file-size checks pass
- All 4,393 desktop tests pass
- Repository pre-push hooks pass
- `just ci` currently stops on pre-existing Clippy warnings in
`crates/buzz-acp/src/queue.rs` and `crates/buzz-agent/src/hints.rs`,
outside this PR diff
Generated with Codex
---------
Signed-off-by: Evan Chen <evanc@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
## Summary
- Makes Projects repository-aware across browsing, branch and tag
selection, local and remote source management, work items, commits, and
contextual repository actions.
- Refines project details into consistent single-column reading surfaces
with a resizable, section-aware context pod, while keeping project
conversations available in the attached chat panel.
- Adds persistent sidebar project navigation, direct entity links,
repository discussion channels, contributor identity matching, and
consistent loading and activity presentation.
- Keeps branch-specific controls on code-oriented sections while Tasks,
Reviews, and Channels remain repository-scoped, reducing misleading
context and actions.
Replacement for #5981 with an identical final tree flattened into one
signed-off commit because the required DCO check suite remained stalled.
### Related issue
N/A
### Testing
- [x] Desktop pre-push checks, TypeScript typecheck, and unit tests
- [x] E2E production build with `pnpm build:e2e`
- [x] Focused Playwright smoke coverage for project workspace, task,
review, commit, sidebar, and contextual-panel behavior
- [ ] Attach final before/after screenshots after design review
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
## Summary
Projects v3 makes repository work shareable, discussion-aware, and
easier to scan in one coherent workspace. People can copy canonical
links, reopen the exact workspace tab, understand issue and pull-request
context at a glance, find related channel conversations, and assign or
unassign issues across Desktop and CLI.
- **Unified workspace** — top-level sections sit above repository
controls in one rounded workspace, with navigation positioned close to
the page heading. README and Files retain branch selection; every
section has a labeled icon header, and Issues and Pull Requests expose
creation from a consistent right-aligned action.
- **Repository management** — the repository selector is always
available, including single-repository projects. Its integrated add flow
lets project owners create a repository manually or select an existing
repository without a separate toolbar button.
- **Readable work-item lists** — issue and pull-request rows use
plain-language context instead of opaque metadata. Files, commits,
issues, pull requests, channels, and contributors share consistent row
density and right-aligned timestamps, while deterministic
fallback-avatar colors keep participants distinct on light backgrounds.
Inbox pull-request metadata wraps between complete phrases and truncates
long channel names instead of compressing copy into narrow columns.
- **Reliable entity links** — projects, repositories, issues, pull
requests, and commits have canonical `buzz://` links, preview cards, OS
deep-link routing, and tab-aware navigation. Reopening the same link
re-applies its destination instead of leaving the user on a locally
selected tab.
- **Related conversations** — repository and work-item views surface
channels discussing the current entity, including participants, channel
navigation, message context, and an explicit notice when discovery
reaches its 500-result cap.
- **Reversible issue ownership** — trusted assignment and unassignment
events work across Desktop, Tauri, `buzz-sdk`, and `buzz issues`.
Assignees appear in project views and the assigned inbox, while
authorized users can remove assignments directly from the assignee row.
Assignment state is derived chronologically from labeled Nostr notes.
Issue authors and repository owners may change any assignee; other users
may only assign or unassign themselves. Shared golden fixtures keep
entity-link grammar and validation aligned across TypeScript and Rust.
The branch also updates `webbrowser` to the patched release for
RUSTSEC-2026-0257.
### Related issue
N/A.
### Testing
- [x] `just ci` — formatting, lint, typechecking, unit tests, and builds
passed
- [x] Full pre-push suite — organization, branch-skew, Desktop checks,
typechecking, and tests passed on the latest push
- [x] `cargo test -p buzz-cli` and focused `buzz-sdk` assignment tests
passed
- [x] Focused Tauri recipient-note and 500-result search-limit tests
passed
- [x] Desktop entity-link and issue-assignment unit tests passed
- [x] Playwright smoke coverage passed for assignment, repeated
entity-link navigation, repository create/select flows, section headers
and actions, timestamp alignment, timeline icons, sentence-style
issue/PR metadata, header spacing, avatar contrast, and Inbox metadata
at stacked and side-rail breakpoints
- [ ] Manual staging pass: link round-trips, Channels tab, assignment
flows, and inbox routing
### Screenshots
Pull requests explain who opened the request, where it lives, and which
branch it comes from; fallback avatars remain visually distinct.

Issues use the same sentence-style hierarchy while keeping status and
recency easy to scan.

The wide Inbox detail keeps author, timestamp, and origin context
readable beside its metadata rail.

[View the complete six-state Projects v3 screenshot
set](https://github.com/block/buzz/pull/5624#issuecomment-5268039672)
and [the compact/wide Inbox
comparison](https://github.com/block/buzz/pull/5624#issuecomment-5268614585).
---
> Supersedes #5624, whose head commit accumulated permanently-queued
required check suites (block-dco-check et al.) that GitHub never
dispatched. History flattened into a single signed-off commit on latest
main; tree verified byte-identical (`git merge-tree`) to merging the
original branch into main.
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Wintermute <3f1797424fd9ad6653a83665c660517777cd7f8c228c0d5907f49e01537f3ca5@buzz.block.builderlab.xyz>
## Summary
- defer foreground resume work until the activation task has returned, a
frame has painted, and a trailing task gets a turn
- centralize app-focus subscribers and remove the broad TanStack
`refetchOnWindowFocus` fan-out
- coalesce relay recovery and preserve an explicit deferred refresh only
for workflow data without a polling/push freshness path
- defer the notification permission native check while keeping blur and
cheap correctness signals immediate
## Why
Buzz Desktop 0.5.10 can spend roughly 1.5 seconds in the WebKit
window-focus listener/microtask checkpoint before returning to the run
loop. Focus currently fans out into query refetches, React polling
updates, relay reconnect/replay, and native work in one activation turn.
This patch establishes an interaction-first foreground boundary rather
than letting those consumers compete with the activating input and first
paint.
## Validation
- focused foreground/workflow/relay tests: 18/18 passed before commit
- `pnpm --dir desktop typecheck`: passed before commit
- pre-commit desktop check and file-size gate: passed
- pre-push desktop check, typecheck, and full desktop unit suite:
4,743/4,743 passed at `704e7b4b6618fafce655bb2b07c7a9fe0fc8c643`
- Princess Donut independent adversarial review: PASS after two
lifecycle/freshness blockers were resolved
## Manual test
1. Install the PR build and use Buzz long enough to populate channels,
home, workflows, agents, and other polling surfaces.
2. Switch to another app for 30-60 seconds.
3. Return by clicking Buzz and immediately click a channel or scroll.
4. Confirm the first interaction and paint are prompt, then confirm
channels/home/workflows refresh and a degraded relay reconnects after
the activation boundary.
5. Repeat while rapidly switching away again to verify no resume work
starts after focus has been lost.
---------
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Fixes#3677.
## Problem
The renderer never quiesces: recurring timers, query polling, and
re-render tickers run at full rate whether the window is visible,
hidden, or minimized. Measured on a live installed app: **27.5% mean
renderer CPU visible vs 28.7% hidden** (60×1s `ps` samples of the
WebContent process; `sample(1)` dominated by
`WebCore::timerFired`/ThreadTimers, microtask checkpoints, JSON parsing,
style matching). Matches all three reproductions in #3677 (macOS
prerelease, Linux/WebKitGTK A/B/A minimize test, stable macOS).
Per-timer instrumentation (dev build, wrapped
`setInterval`/`setTimeout`/rAF) attributed the recurring work: `useNow`
60 fires/min, 40 active TanStack refetch intervals, agent-turn pruning
12/min, auto-restart ticks, huddle/reminder polls — none
visibility-gated.
## Fix (two-tier gating, standard mechanisms only)
Two separate signals in `desktop/src/shared/lib/useDocumentVisible.ts`,
because they mean different things and (see residuals) are delivered
differently on macOS:
- **`useDocumentVisible`** — true Page Visibility only
(`document.visibilityState`). Gates local UI work that must keep running
on a visible-but-unfocused window: `useNow` relative clocks, agent-turn
pruning, huddle bar state/model-status polling, auto-restart tick.
Hidden ⇒ paused; `useNow` snaps to fresh `Date.now()` on return.
- **`useAppFocused`** — visible AND `document.hasFocus()`. Gates network
refetch polling only (`useFocusedRefetchInterval`, ~15 query families:
forum/home/agents/channels/templates/emoji/user-status/projects/workflows/persona-catalog/pulse/presence-list).
TanStack's `focusManager` is wired to this signal (idempotent, single
install) with `refetchOnWindowFocus: true`, so stale queries refresh
promptly on return. Deliberate side effect, documented in code: query
retries pause on blur; mutations and the presence heartbeat (`retry: 0`)
are unaffected.
- **Never gated:** reminder due-notification poll (fires while
hidden/unfocused — extracted to `reminderNotificationPoll.ts` with
regression test), huddle pipeline hot-start (`check_pipeline_hotstart`
survives backgrounding for the duration of a huddle), relay stall
watchdog, presence heartbeat. Live WebSocket delivery untouched
throughout.
- Huddle model-status indicator now clears only on huddle phase end, not
on visibility/focus changes.
## Validation
- Instrumented dev build, populated channel, fires/min:
**visible+focused** unchanged (`useNow 60 / prune 12 / watchdog 6 /
query 4 / auto-restart 4 / low-rate huddle/reminder/presence`);
**visible+blurred**: query polls 0, UI clocks continue (`useNow 60 /
prune 12`), reminders 2, presence live; **truly hidden**: only watchdog
6, reminders 2, presence ~2 — everything else 0. Return restored
visible+focused, selection preserved, queries refreshed.
- Hide-vs-blur decomposition (instrumented probe instance,
AppleScript-driven): on macOS WKWebView, Cmd-H / minimize / full
occlusion did **not** reliably produce `visibilityState === "hidden"` —
they reliably produced focus loss. The CPU-dominant quiescence path on
macOS is therefore the focus gate; the visibility gate is exercised
fully on platforms that report hidden (e.g. WebKitGTK minimize per the
Linux repro).
- Gate-regression tests: signal separation, `useNow` hidden-pause +
fresh-snap on return, focus-gated interval pause/resume-with-refresh,
reminder delivery while hidden+unfocused (5 new, plus primitive wiring
tests).
- Push gate: desktop check, typecheck, full desktop suite **4549/4549**
at `1237548d1`.
## Known residuals
- **macOS hidden-signal limitation:** because WKWebView rarely reports
`hidden` on app-hide/minimize, hidden-only consumers (`useNow`, prune,
huddle UI polls) may keep ticking on macOS when the app is hidden. These
are cheap local timers; the expensive network polling still quiesces via
focus loss, which is what the measured 28% CPU was attributed to. If the
residual local-timer cost proves measurable, the follow-up is bridging
Tauri window hidden/minimized events into the visibility signal.
- End-to-end CPU confirmation on a packaged build is the post-merge
follow-up (against the 28% idle baseline).
- Visible-state costs (skeleton animation pileups on stuck loading
views, per-poll JSON payload churn) are intentionally out of scope —
separate follow-up issue.
---------
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
## Summary
Follow-up batch on the Projects overview (continues merged #1677):
- **Repository access restrictions** — repositories the viewer can't
reach are surfaced with a reason instead of failing silently.
Channel-ACL denials (which arrive as the same 404 as a missing repo, for
anti-enumeration) are re-classified using the repository's channel
binding and the viewer's memberships (`useRepositoryAccess.ts`,
`projectRepoAvailability.ts`).
- **Projects loads in seconds instead of minutes** — enumeration no
longer crawls every kind:5 deletion event on the relay. It fetches
project/repo announcements first, then queries deletions scoped to those
coordinates via chunked `#a` filters (3 queries instead of hundreds on
staging).
- **Activity feed layout polish** — bare event-type glyph beside the
headline (no badge circle), timeline spine runs through the avatars
connecting consecutive cards, linkable actor/project names are bold in
theme foreground, rounded hover state, alignment fixes.
- **Create button pinned** — the "+" create menu is pinned to the pane's
top-right corner (equal 16px insets) and no longer scrolls away with the
page header.
- **List controls as a table header** — the scope selector (left) and
sort + layout toggle (right) render as the first row of the list
container on the Projects/Repositories/PRs/Issues tabs; in card view the
identical bar stands alone with the cards below
(`ProjectsListHeaderBar.tsx`).
- **Repository rows show the git location** — subtitle is
`github.com/org/repo` for external repos or `owner/repo` (resolved
profile name) for Buzz-hosted ones, instead of repeating the project
name (`repositoryDisplayPath`).
- **Uniform work-item row heights** — issue rows previously ran the
author chip in inline flow, letting the 20px avatar grow the line box
~3px taller than PR rows; both lists now share the same flex subtitle.
📸 Screenshots: [feed layout / pinned
button](https://github.com/block/buzz/pull/5073#issuecomment-5214114069)
· [list header / repo subtitles / row
heights](https://github.com/block/buzz/pull/5073#issuecomment-5217480712).
Note: two empty `chore: retrigger CI` commits exist on the branch from
working around the Aug 6 GitHub Actions incident; happy to drop them
with a signoff rebase before undrafting if preferred. Latest `main` is
merged in (`a0cc35220`).
## Test plan
- [x] Desktop unit tests (4,493 pass after merging main), Biome, tsc
- [x] New unit tests for scoped deletion enumeration and repo
availability re-classification
- [x] New unit tests for `repositoryDisplayPath` (external, Buzz-hosted,
unresolvable)
- [x] Screenshot verification of feed layout, connector spine, and
pinned button (top + scrolled states) — posted to the PR
- [x] Screenshot verification of the list header row (list + card), repo
subtitles, and matching PR/issue row heights — posted to the PR
- [ ] Manual pass against staging (projects list load time,
restricted-repo states)
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
- admit relay-discovered agents to autocomplete when their response
policy authorizes the viewer
- require authorization in the exact active stream/forum channel for
mentions, while keeping community-wide discovery for member invitation
- fail closed for relay-only agents in DMs and unresolved composer
contexts
- re-authorize cached autocomplete rows after policy/channel changes so
stale agent suggestions cannot leak back in
- preserve managed-agent behavior and explicitly reject stale
agent-marked channel members absent from both live directories
## Validation
- `pnpm --dir desktop test` — 4,288 passed
- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop check`
- `pnpm --dir desktop build:e2e`
- focused Playwright mention matrix — 12 passed
- focused Playwright member-invitation matrix — 2 passed
- pre-push hooks after rebase to current `origin/main` — desktop check
and 4,288 tests passed
- independent correctness/privacy re-review cleared with no remaining
blocker
## Related competing PRs
This supersedes or overlaps #2333, #3056, #4242, #4137, #2314, #4058,
and #2605. This version adds exact-channel authorization, fail-closed
DM/context handling, cached-row reauthorization, forum coverage,
outbound mention-tag coverage, explicit stale-member coverage, and
add-member discovery coverage.
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary
- adopt the finalized NIP-MP project model so one project can enumerate
and switch between multiple NIP-34 repositories
- add project and repository navigation, activity summaries,
existing-repository attachment, and repository access-channel management
- preserve privacy-safe activation provenance for agent-authored
patches, pull requests, issues, and associated commits
## Test plan
- [x] Run desktop typecheck and unit tests
- [x] Run focused NIP-MP, repository access, and provenance tests
- [x] Run Rust formatting and desktop lint checks
- [x] Run the complete pre-push suite after merging current `main`
- [ ] Manually verify project creation, repository attachment,
switching, and access repair on staging
- [ ] Manually verify public-channel and private-agent origin labels on
newly created Git activity
Related: [#4695](https://github.com/block/buzz/pull/4695)
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
## Summary
- let the Projects page surface flow through repository, pull request,
and issue list/grid layouts
- remove opaque fills from project-detail content across Overview,
Files, Commits, Issues, Pull Requests, and Contributors
- preserve borders, hover feedback, and intentional nested fills for
code, inputs, badges, and warnings
## Related
Follow-up to #3416.
## Testing
- `pnpm exec biome check` on the changed Projects UI and E2E files
- `pnpm build:e2e`
- focused Playwright smoke coverage for overview, subsection list/grid,
and project-detail transparency (3 passed)
- pre-commit checks passed
- desktop pre-push checks passed; the unrelated integration hook remains
blocked by a stale local checksum for migration 25
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
The Projects overview now lets the page surface flow through its metrics
and activity cards instead of stacking filled panels. Borders and hover
feedback remain, preserving grouping and interaction cues without the
heavy nested background.
### Related issue
None found.
### Testing
- `pnpm exec biome check
src/features/projects/ui/ProjectsOverviewPanel.tsx
src/features/projects/ui/ProjectsActivityFeed.tsx
tests/e2e/project-pr-review.spec.ts`
- `pnpm build:e2e`
- Focused Playwright smoke test: `project overview does not paint a
background behind its cards` (passed)
- Relevant desktop pre-push checks passed; the unrelated integration
gate was blocked by a stale local checksum for migration 25
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Closes#3527.
Repos announced via vanilla NIP-34 (kind:30617 without a `buzz-channel`
tag) 404 forever: the SEC-005 read gate requires a channel-membership
ACL, and nothing tells the author why or how to fix it. Per the ruling
in the originating thread, this ships **bind/rebind tooling plus a
narrow author-only remediation carve-out** — the shelved owner-circle
approach is intentionally absent.
## Relay
- **`api/git/binding.rs` (new):** shared tri-state binding resolver —
`Bound(uuid)` / `NotBound` / `Broken`. First-tag, fail-closed: a
malformed `buzz-channel` tag is `Broken`, never conflated with "no tag".
Both gates use it.
- **Read gate (`transport.rs`):** a **never-bound** repo read by **its
own announcement author** still returns 404 (status byte-identical to
the generic denial) but the body carries remediation: `run: buzz repos
bind --id <repo> --channel <channel-uuid> — …`. This leaks nothing — the
author announced the repo, and only the author can rebind (30617 is
keyed by `(author, d)`). `Broken` bindings stay generic-denial for
everyone, including the author (revocation shape).
Bound-to-nonexistent-channel stays generic (phase 1; ingest validation
is phase 2).
- **Push gate (`policy.rs`):** unbound denial now returns
`GIT_NO_CHANNEL_BINDING_BODY`. A deploy-skew test pins that the body
carries both the new token (`no_channel_binding`) and the legacy phrase
(`"no channel binding"`) so already-shipped desktops keep matching.
**(Review r1, blocker 2)** `Broken` no longer collapses into "unbound":
it denies 403 `invalid channel binding` for *everyone — including the
announcement owner —* **before** the owner short-circuit, matching the
read gate's fail-closed posture. The remediation token stays
NotBound-only.
- **`ingest.rs`:** side-effect failure `warn!` → `error!` — prod runs
`RUST_LOG=error`, so these failures were invisible during triage.
## Contract
- **`buzz-core/git_perms.rs`:** `GIT_NO_CHANNEL_BINDING_TOKEN` /
`GIT_NO_CHANNEL_BINDING_BODY` consts as the declared cross-component
contract; relay tests and desktop matcher both build on them.
## CLI
- **`buzz repos bind --id <repo> --channel <uuid>`** — rebinds an
existing announcement, preserving other tags.
- **(Review r1, blocker 1)** **`--channel` on `buzz repos create`** —
optional; injects exactly one shape-validated `buzz-channel` tag at
creation via a pure `build_create_announcement` builder, so the primary
create command stops producing repos the relay 404s. UUID
existence/membership stays the relay's authority at git-access time
(same TOCTOU posture as `repos bind`). Overlaps with #3594 (open, head
6bbe38459) — happy to reconcile whichever lands first; this branch also
carries the bind path and tag preservation.
## Desktop
- **Rust:** new `commands/project_git_merge_error.rs` (extracted from
`project_git_workflow.rs` to respect the 1000-line ratchet); maps the
token to a structured `no_channel_binding` error carrying the bind
command.
- **TS:** new `features/projects/lib/projectBranchErrors.ts` + tests —
dual matcher (new token AND legacy spaced phrase);
`ProjectBranchDialogs.tsx` uses it.
## Tests / verification (at head f914c7066, base 581baa625)
- Workspace `cargo test` green; `clippy -D warnings` clean; desktop Rust
1859 pass; TS 3780 pass; tsc/biome/file-size checks pass. Pre-push hooks
re-ran all suites at the pushed head.
- Postgres-gated `sec005_read_gate_tests`: all 6 pass, including
`read_gate_gives_author_of_unbound_repo_remediation_body` — asserts 404
status, `text/plain` content-type, and exact body bytes, distinguishing
remediation from generic denial (a blind `is_err()` can't).
- **New (review r1):** `buzz-cli` emitted-event tests —
`create_with_channel_emits_exactly_one_binding_tag`,
`create_without_channel_emits_no_binding_tag`,
`create_rejects_malformed_channel_uuid` (266/266 pass). Postgres-gated
`push_gate_denies_owner_through_broken_binding` — owner +
malformed-first/valid-second binding → 403 generic body without the
remediation token; never-bound control stays 200, pinning the denial to
`Broken` specifically.
- e2e git tests now bind announcements to a real channel via a
`create_test_channel` helper.
---------
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
## Summary
Pull requests and issues that mention you now appear as
repository-scoped Inbox conversations, so project work can be reviewed
without first navigating to Projects.
Opening a project item resolves its current canonical state and reuses
the existing review, comment, merge, and issue actions. Repository-aware
grouping keeps identical event IDs from different repositories separate,
while loading, missing-data, and partial-query states avoid exposing
stale actions.
### Related issue
None found.
### Testing
- `node --import ./test-loader.mjs --experimental-strip-types --test
src/features/home/lib/projectInbox.test.mjs` — 6 tests passed
- `CI=1 pnpm exec playwright test tests/e2e/project-inbox.spec.ts
--project=smoke` — passed
- Pre-push desktop, mobile, Tauri, and Rust checks — passed
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
Project issues, pull requests, reviews, and commit details no longer
flatten rich content into inert or plain text. They now share the
message markdown and media pipeline, preserving NIP-92 `imeta` metadata
so links, images, and videos render consistently.
Commit bodies are fetched only when a single-commit detail view is
opened, keeping list queries lightweight while exposing full context
beside the diff.
### Related issue
None found.
### Testing
- Pre-push suite: `desktop-check`, `desktop-test`, `desktop-tauri-test`,
`rust-tests`, and `mobile-test`
- Project issue and pull request regression tests cover preserving
attachment metadata on roots, updates, and comments
- The project commit detail smoke scenario verifies linked text, images,
and video in commit bodies
Signed-off-by: Thomas Petersen <thomasp@squareup.com>