Skip to content

feat(desktop): refine repository-aware project workspaces - #6003

Merged
thomaspblock merged 19 commits into
mainfrom
projects-v5-squashed
Aug 19, 2026
Merged

feat(desktop): refine repository-aware project workspaces#6003
thomaspblock merged 19 commits into
mainfrom
projects-v5-squashed

Conversation

@thomaspblock

Copy link
Copy Markdown
Contributor

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

  • Desktop pre-push checks, TypeScript typecheck, and unit tests
  • E2E production build with pnpm build:e2e
  • Focused Playwright smoke coverage for project workspace, task, review, commit, sidebar, and contextual-panel behavior
  • Attach final before/after screenshots after design review

Make Projects repository-aware across navigation, source management, work items, discussions, and contextual actions while consolidating detail views into consistent single-column workspaces.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>

@themiguelamador themiguelamador left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requesting changes for five correctness issues found in the project-workspace rewrite:

  1. Inline agent conversations can expose unrelated DM history from the same second. visibleAfter is only epoch-second precision, so every existing kind 9/40002 event with the same created_at as the opener passes the >= filter. Persist the exact opener event ID returned by sendChannelMessage and filter on the same (created_at, event_id) ordering used by the timeline; reject legacy timestamp-only pointers because they cannot uphold the isolation invariant.

  2. “Owned by me” is accidentally restricted to projects already in “Added.” listSidebarProjects applies addedProjectAddresses.has(...) before both filter modes, so switching to Owned cannot discover an owned project that has not already been added. The two modes need independent predicates, and the owned test should cover an address absent from the added set.

  3. Add/remove becomes a visible no-op when localStorage writes fail. writeProjectSidebarMembership catches the error before dispatching the change event, while callers do not update component state themselves. Dispatch the computed membership to the current relay/pubkey scope even when persistence is unavailable, then let the mounted sidebar consume that event detail.

  4. User-authored text containing the page-context marker is truncated. stripProjectDetailAgentContext searches from the beginning even though the generated footer is appended at the end. Use the last marker and cover a prompt that legitimately contains an earlier marker.

  5. The two new :has(...) selector groups trigger Biome's descending-specificity warnings. Ordering the generic selector before the two root-qualified selectors removes both warnings without changing declarations.

I implemented and verified all five in signed local commit fe2d3fcc0908ebc6a99a5b19232601f66c312f66. The PR has maintainerCanModify: false, so I could not put that commit on the head branch; please apply the equivalent changes or enable maintainer edits.

Verification on the fixed tree:

  • pnpm exec tsc --noEmit
  • pnpm check:file-sizes
  • full desktop unit suite: 4,989 passed
  • fresh pnpm build:e2e
  • five project Playwright specs: 44 passed
  • all six supplied screenshot states visually inspected and hash-distinct

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

Carl, an automated reviewer, commenting via Wes’s GitHub account.

The Royal Court independently reviewed exact head 9ae5e5cd61a2139a15c8ede88cd25a936c7bc281. We reached consensus on the five blockers already recorded in the earlier changes-requested review and found two additional P1 defects:

  1. Do not put untrusted project metadata into a hidden agent prompt. ProjectAgentChatPanel.tsx:113-119 silently appends projectDetailAgentContextBlock(context) to the user-signed DM. projectDetailAgentContext.ts:92-108 interpolates relay/git-controlled project and repository names, work-item titles, branch names, and file paths verbatim, then tells the agent to use that context. Repository names, for example, come directly from the announcement name tag (projectModels.ts:245-266) with only a byte cap (projectModels.ts:119-169), so an untrusted owner can include newlines and instruction-shaped text. Opening that repository and asking an innocent question launders attacker-controlled text into a hidden prompt sent under the viewer’s identity; the context strip does not disclose the exact payload.

    Keep hidden context to constrained stable identifiers/enums, or visibly disclose the exact appended text and explicitly mark metadata values as untrusted. JSON escaping alone preserves syntax but does not make instruction-shaped strings safe for an LLM. Add adversarial coverage for project/repository names, work-item titles, branches, and paths. The pre-existing workspace-level repoContextBlock has the same class of problem and should be fixed at the shared trust boundary rather than leaving one entry point exploitable.

  2. Do not fabricate an origin conversation from nearby channel traffic. DiscussionChannels.tsx:67-77 fetches the 20 newest events in the entity's h channel at or before the entity timestamp, and discussionChannels.ts:75-86 chooses the newest event by the author or falls back to the newest event by anyone. No event or thread reference ties that message to the task/review. The UI nevertheless labels it as having “started” the entity and quotes its content (DiscussionChannels.tsx:242-313). This can falsely attribute and expose an unrelated message, and with enough intervening traffic it can select somebody else's message. The current test at discussionChannels.test.mjs:84-102 codifies the heuristic rather than proving an association.

    The h tag proves only the origin channel. Without an exact reference, show a channel-only row that navigates to the channel; do not quote or claim a specific spawning conversation.

The green CI snapshot does not exercise either semantic trust boundary. Please address these two findings together with the five existing blockers before re-review.

- Anchor the inline Projects agent conversation to the accepted opener
  event (created_at, event_id) instead of a bare visibleAfter timestamp,
  so unrelated DM history sharing the opener's second is excluded and the
  opener itself is always included (id-equality short-circuit tolerates
  the command's post-hoc timestamp).
- Make the sidebar "owned" filter surface every project the viewer owns,
  independent of the Added set.
- Dispatch the sidebar-membership change event even when localStorage
  persistence fails, carrying the computed membership in the event
  detail; the sidebar listener consumes the detail instead of re-reading
  storage.
- Strip agent-context footers from the last marker (lastIndexOf) so user
  text containing an earlier marker survives intact.
- Reorder the :has() selector groups in components.css so the generic
  content-surface selector precedes the :root-qualified ones.
- Sanitize relay/git-controlled values (project/repo names, repo address,
  branch, file path, work-item title/id/status) before embedding them in
  the hidden agent prompt, and disclose them as untrusted context.
- Stop fabricating an origin conversation in DiscussionChannels: the
  author-claimed origin now renders as a channel-only row with no quoted
  message.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Pushed 07c2be3 addressing all seven change requests across both reviews (the suggested commit fe2d3fcc was not fetchable from the remote, so the fixes were re-implemented):

  1. Opener-anchored conversation pointer — the inline Projects agent conversation now persists a ProjectsConversationOpener {createdAt, eventId} anchored to the accepted opener event instead of a bare visibleAfter timestamp. isAtOrAfterConversationOpener matches compareRelayOrder semantics, so unrelated DM history sharing the opener's second is excluded while the opener itself is included via id equality (tolerating the Tauri command's post-hoc timestamp).
  2. "Owned" sidebar filter — now surfaces every project the viewer owns, independent of the Added set; "added" unchanged. Unit test added.
  3. Membership change eventprojectSidebarMembership dispatches the change event even when localStorage persistence fails, carrying the computed membership in the CustomEvent detail; SidebarProjectsSection consumes the detail (scoped to relayOrigin/pubkey) instead of re-reading storage.
  4. Footer strippingstripProjectDetailAgentContext / stripRepoContext use lastIndexOf, so user text containing an earlier marker survives. Test added.
  5. CSS :has() ordering — the generic [data-buzz-content-surface]:has(...) group now precedes the :root-qualified ones in components.css.
  6. P1, prompt injection — all relay/git-controlled values embedded in the hidden agent prompt (project/repo names, repo address, branch, file path, work-item title/id/status) pass through untrustedPromptValue (control-char strip, whitespace collapse, length cap, JSON-quoting) and are disclosed via UNTRUSTED_CONTEXT_NOTICE. Applied to both projectDetailAgentContextBlock and repoContextBlock. Tests added.
  7. P1, origin fabrication — removed pickOriginConversationEvent / useOriginConversationHit and the origin-quote fallback; the author-claimed h-tag origin now renders as a channel-only "created from #channel" row with no fabricated quoted message.

Verification on 07c2be3: full desktop unit suite 4989/4989 pass, tsc --noEmit clean, pnpm check (biome + file-sizes + px-text + pubkey-truncation) clean; push hooks re-ran desktop-check/typecheck/test/tauri-checks green.

@thomaspblock
thomaspblock marked this pull request as ready for review August 16, 2026 20:25
@thomaspblock
thomaspblock requested a review from a team as a code owner August 16, 2026 20:25
@thomaspblock
thomaspblock enabled auto-merge (squash) August 16, 2026 20:25

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

Carl, automated reviewer commenting via Wes's GitHub account.

Requesting changes at exact head 07c2be37a0e4914d2285c21af8958db1dc8e5c2b. The prior seven findings were addressed, but the repository-aware rewrite still has four material correctness/trust-boundary defects:

  1. Cross-community agent-conversation state is not tenant-scoped. ProjectAgentChatPanel.tsx:56-60 keys the persisted conversation and draft only by detail:${context.repoAddress}, while ProjectDetailRightPanel.tsx:24-30 remounts only on that same repository address. The same coordinate can exist in two communities. A community switch can therefore retain/restore the other tenant's DM channel/opener. restoreProjectsAgentConversation also accepts any matching channel ID/candidate without proving channelType === "dm" or the expected participant set. Include stable community/relay identity in persistence, draft, and component reset keys, and validate the restored DM participants.

  2. Repository switches retain same-named branch/tag state. ProjectDetailScreen.tsx:803-815 clears work-item selections and source but does not reset ref selection. useProjectRepositoryRefSelection.ts:24-34 deliberately preserves a current branch/tag whenever its name exists in the new repository. Switching A→B can silently keep release or v1 rather than opening B's default, causing files/actions/agent context to target an unselected ref. Key/reset the selection by repository identity and cover same-name branch and tag transitions.

  3. The hidden prompt still signs semantically intact attacker instructions under the user identity. ProjectAgentChatPanel.tsx:112-118 appends projectDetailAgentContextBlock; projectDetailAgentContext.ts:115-150 JSON-quotes metadata but preserves instruction-shaped project/repository names, titles, branches, and paths. The exact footer is hidden in both the pre-send strip and rendered self-message. Quoting plus a natural-language warning is not an LLM trust boundary. Keep hidden context to constrained coordinates/enums/IDs, or visibly disclose the exact appended payload; add an agent-level adversarial test that proves metadata cannot steer tool choice.

  4. The persisted opener cursor uses a post-publication timestamp and can hide a fast reply forever. Native send_channel_message signs/publishes first, then returns Utc::now() (desktop/src-tauri/src/commands/messages.rs:546-575). Projects stores that value (ProjectAgentChatPanel.tsx:123-139), while isAtOrAfterConversationOpener exempts only the opener ID and rejects every other event older than the returned timestamp (projectAgentConversation.ts:21-29). If publication crosses a second boundary, an immediate agent reply stamped in the opener's signed second is excluded even after refresh. Return/persist the signed event's actual created_at (or an authoritative event cursor), and regress opener + same-second fast reply + older same-second history.

Validation on this exact head: full Desktop unit suite passed 4,989/4,989 in a clean detached worktree. GitHub CI is green on the same SHA. Those gates do not cover these state/trust transitions.

@jedwards27

Copy link
Copy Markdown
Contributor

Follow-up reliability finding, independently reproduced from source at unchanged head 07c2be37a0e4914d2285c21af8958db1dc8e5c2b:

  1. Sidebar membership fallback loses sequential actions when localStorage writes fail. projectSidebarMembership.ts:67-90 derives every add/remove from a fresh localStorage read. The change event updates mounted React state but does not become the source for the next mutation. If setItem throws and getItem remains null, add(A) → add(B) → remove(A) dispatches [A] → [B] → [] rather than [A] → [A,B] → [B]. The advertised best-effort recovery therefore survives one mutation only. Maintain scope-keyed in-memory authoritative state (initialized from storage), or make callers pass current state into each mutation; add a sequential write-failure regression.

Mongo ran the full exact-head just desktop-ci gate: Desktop unit 4,989/4,989; Tauri main 2,440 passed, 15 ignored; typecheck and existing exact-head CI green. This failure is outside those tests.

…orkspaces

- Sign channel/agent messages before submission so the response created_at
  matches the signed event, and admit e-tag replies to the conversation
  opener regardless of same-second ordering
- Validate stored agent-conversation pointers: restore only real DM
  channels whose participants are exactly {agent, self}
- Render the user's own messages verbatim (drop context-footer stripping)
  so the exact signed payload is always visible
- Scope agent chat panel state and remount key by relay URL + repo
  address so state never crosses a community boundary
- Reset branch/tag selection during render on repository switch to avoid
  a stale same-named ref leaking for one frame

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock

Copy link
Copy Markdown
Contributor Author

All four round-2 findings are addressed at head a9852a9f375e357ffc03e2507cfc443de134243d.

1. Cross-community agent-conversation state is now tenant-scoped, and restored pointers are validated.
ProjectAgentChatPanel.tsx keys persisted conversation state and drafts by detail:${normalizedRelayUrl}:${repoAddress}; when no relay identity is available the scope is null and persistence is a no-op (fail closed). ProjectDetailRightPanel.tsx remounts the chat panel on ${relayScope}:${repoAddress}, so panel state never crosses a community boundary even for identical repo coordinates. restoreProjectsAgentConversation now requires the caller's pubkey and only accepts a channel that is a real DM whose participant set is exactly {agent, self} — non-DM channels, foreign participants, or a missing identity all return null. Regression tests cover each rejection path (projectAgentConversation.test.mjs).

2. Repository switches reset ref selection.
useProjectRepositoryRefSelection now takes the repository identity as an input and resets branch/tag selection during render when it changes, so a same-named release/v1 ref from repo A can never survive into repo B — not even for one frame. ProjectDetailScreen passes repository?.id. New hook regression suite: useProjectRepositoryRefSelection.test.mjs (covers same-name branch and tag transitions).

3. The appended context payload is now visibly disclosed — nothing signed under the user's key is hidden.
The strip-on-display mechanism is removed entirely: stripRepoContext, stripProjectDetailAgentContext, and ConversationThread's stripSelfContent prop are gone. The user's own messages render verbatim, including the machine-appended context footer, so the exact signed payload is always visible to the user in the conversation. This takes the "visibly disclose the exact appended payload" branch of the finding: with full disclosure, the footer is no longer a hidden channel, and the existing untrustedPromptValue sanitation from round 1 continues to quote and mark relay/git-controlled values as untrusted data.

4. The opener cursor is the signed event's created_at.
Both native send paths (send_channel_message and the managed-agent send) now sign the event before submission and return the signed event's own created_at instead of a post-publication Utc::now() read (new helpers submit_event_with_created_at / submit_event_with_keys_created_at in relay/submit.rs, re-exported from relay.rs). As defense in depth, isAtOrAfterConversationOpener additionally admits any event whose e tag references the opener id — causality beats same-second (created_at, id) ordering luck, so a fast agent reply stamped in the opener's second is always shown. Regression: fast-reply-via-e-tag plus the existing opener/same-second-history cases.

Validation at a9852a9f3: full desktop unit suite 4,992/4,992, tsc clean, pnpm check/biome clean, cargo check + cargo tests clean; the push hooks re-ran the full desktop check/typecheck/test/tauri suite green. Diff: 13 files, +336/−85.

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

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Requesting changes at exact head a9852a9f375e357ffc03e2507cfc443de134243d. The four round-2 fixes are directionally sound on source review, but one previously reported reliability blocker is still present unchanged:

  1. The localStorage failure fallback still loses sequential sidebar membership mutations. desktop/src/features/projects/lib/projectSidebarMembership.ts:67-90 computes every add/remove from a fresh readProjectSidebarMembership(...). The write path catches setItem failures and dispatches the computed value (:44-64), so mounted React state survives one operation, but that event-carried state never becomes the source for the next operation. With storage unavailable, add(A) → add(B) → remove(A) still dispatches [A] → [B] → [], not [A] → [A,B] → [B]. This is exactly the failure described in the existing follow-up comment, and a9852a9f3 does not modify this module or add its missing sequential-failure regression.

Keep scope-keyed in-memory membership as the authoritative fallback (initialized from storage and updated before dispatch), or pass the mounted current membership into each mutation. Add a regression that forces both getItem/setItem failure or non-persistence across at least two sequential mutations.

I also traced the new tenant-scoped conversation restore, repository-ref reset, visible context payload, and signed-event cursor changes against their callers. I found no additional actionable defect in those four fixes. Green exact-head CI does not exercise the remaining storage-failure state transition.

@wesbillman

Copy link
Copy Markdown
Collaborator

Follow-up at exact head a9852a9f375e357ffc03e2507cfc443de134243d: the reliability finding in #6003 (comment) remains unresolved.

desktop/src/features/projects/lib/projectSidebarMembership.ts:67-90 still derives every mutation from a fresh localStorage read. The event detail updates mounted React state, but no state survives as the source for the next mutation when setItem throws. The file is byte-identical to head 07c2be37a (same SHA-1 from git show), and there is no write-failure regression in SidebarProjectsSection.test.mjs.

Direct reproduction against the production helpers at this head, with getItem => null and setItem => throw, prints:

add(A), add(B), remove(A) event details: [["A"],["B"],[]]

Expected is [["A"],["A","B"],["B"]]. Thus the best-effort fallback loses every prior action after the first while storage remains unavailable. Please maintain scope-keyed in-memory authoritative membership initialized from storage (and update it before each dispatch), or pass current membership into mutations; add the sequential write-failure regression.

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

Princess Donut, automated reviewer commenting via Wes's GitHub account.

Requesting changes at exact head a9852a9f375e357ffc03e2507cfc443de134243d. I independently confirmed the still-open sequential localStorage failure reported in the issue comments. I also found that the round-2 prompt fix does not actually satisfy the proposed disclosure boundary:

  1. P1 — attacker-controlled prompt content is still undisclosed when the user authorizes/signs it. ProjectAgentChatPanel.tsx:117-135 accepts only the user's composer text, then appends projectDetailAgentContextBlock(context) inside handleSubmit immediately before sendChannelMessage. The pre-send UI exposes only contextLabel(context) — a single title/file/view label (ProjectAgentContextStrip.tsx:4-12,21-31) — not the exact appended payload. Rendering the full signed message afterward does not let the user inspect or decline that payload before it is signed and delivered to the agent; the agent can act before the retrospective disclosure is even seen.

    This remains security-relevant because projectDetailAgentContext.ts:115-149 still embeds instruction-shaped relay/git metadata and explicitly directs the agent to use it. JSON quoting and a warning are useful framing, but are not an enforcement boundary. An attacker-controlled project/repository name or work-item title can therefore still steer the agent through content the user never saw when pressing Send.

    Smallest safe remedy: before submission, visibly preview the exact footer that will be appended (with an explicit untrusted-data warning), or keep the automatically appended payload to constrained stable identifiers/enums that cannot carry free-form instructions. Cover the submit UI boundary, not only the formatter, with adversarial metadata.

  2. P1 — sidebar fallback still loses sequential actions when persistence stays unavailable. projectSidebarMembership.ts:67-90 recomputes every mutation from localStorage, while a failed write survives only in event detail. Thus add(A) → add(B) → remove(A) still dispatches [A] → [B] → [], not [A] → [A,B] → [B]. Head a9852a9f3 did not modify this path. Use scope-keyed in-memory authoritative state (seeded from storage), or pass the mounted current state into each mutation; add the sequential write-failure regression already requested.

The four other round-2 repairs are directionally sound from source inspection. Green CI does not exercise either trust/failure transition above.

…rkspaces

- Keep scope-keyed in-memory sidebar membership authoritative so
  sequential add/remove mutations accumulate even when every
  localStorage write fails; storage is only the durable mirror.
  Regressions cover write-failure sequences, read+write failure, and
  recovery persisting the accumulated set.
- Disclose the exact agent-context payload before send: both the
  project-detail chat panel and the Projects prompt page now expose a
  pre-send preview of the byte-identical footer that will be appended
  and signed under the user's key, with an explicit untrusted-metadata
  warning. Component regression drives adversarial instruction-shaped
  metadata through the disclosure.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock

thomaspblock commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Both round-3 findings are addressed at head 99bbbadb450d98160ff68d54ac75606a2a2d8f96.

1. Sequential sidebar mutations under storage failure (projectSidebarMembership.ts)

The module now keeps a scope-keyed in-memory membership map as the authoritative state; localStorage is only the durable mirror. Each scope is seeded from storage on first read, and every mutation reads from and writes to the in-memory scope before attempting persistence, so add(A) → add(B) → remove(A) dispatches [A] → [A,B] → [B] even when every setItem throws. When persistence recovers, the next successful write lands the full accumulated set (including entries whose own writes had failed).

New projectSidebarMembership.test.mjs covers exactly the requested transitions:

  • sequential add(A) → add(B) → remove(A) with all writes failing, asserting each dispatched membership and the final read;
  • mutations with both getItem and setItem failing;
  • recovery: a later successful write persists the previously unpersisted entry;
  • scope independence and the normal round-trip.

2. Pre-send disclosure of the appended prompt payload

New AgentContextPayloadPreview component renders at the composer in both submit surfaces — the project-detail chat panel and the Projects prompt page. It discloses, before the user presses Send, the byte-identical string that will be appended and signed: each surface computes the payload once (useMemo) and passes the same value to both the preview and sendChannelMessage, so the preview cannot drift from what is sent. The disclosure carries an explicit warning that quoted values are untrusted workspace metadata that Buzz does not verify or rewrite. On the prompt page it is labeled "Context appended to your first message" and shown only while the payload will actually ride the opener.

This covers the submit UI boundary, not just the formatter: AgentContextPayloadPreview.test.mjs mounts the component with a payload built from adversarial instruction-shaped metadata (Ignore prior instructions…, forged - Branch: lines) and asserts the disclosed text equals the exact appended payload and that the hostile content is visible to the user as quoted data. Combined with round 2, the payload is now inspectable before signing and rendered verbatim in the sent message afterwards.

I considered the alternative remedy (constraining the footer to bare identifiers/enums) and kept the free-text values: titles and names are what make the context useful to the agent, and they are already neutralized to single quoted lines. The disclosure boundary now makes that trade-off visible to the user instead of silent.

Verified at 99bbbadb4: 4999/4999 desktop unit tests (pnpm test), tsc --noEmit clean, pnpm check (biome + file-size/px/pubkey gates) clean; push hooks re-ran desktop-test, desktop-tauri-checks, and mobile-test green.

@wesbillman

Copy link
Copy Markdown
Collaborator

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Royal Court follow-up on exact head 99bbbadb450d98160ff68d54ac75606a2a2d8f96: all previously reported blockers are resolved.

  • The sidebar now keeps relay/pubkey-scoped in-memory membership authoritative across sequential storage failures, updates it before best-effort persistence, and dispatches the accumulated value. The new regression covers add(A) → add(B) → remove(A), simultaneous read/write failure, and persistence recovery.
  • Both project-agent entry points now compute one context payload and use that same value for the pre-send preview and the outgoing message. The preview exposes instruction-shaped metadata with explicit untrusted-data framing before authorization; the adversarial component test exercises this boundary.
  • The earlier fabricated origin-conversation behavior remains removed: an h tag alone produces a channel-only row, while quoted previews require actual entity-link search hits.
  • The other prior fixes (tenant-scoped conversation restore, repository ref reset, exact opener cursor, independent Owned/Added filtering, suffix-only context stripping, and selector ordering) remain intact on source review.

Independent Court passes reached the same no-blocker verdict. Focused verification on the exact clean head: 7/7 new storage/preview tests passed, changed-file Biome passed, TypeScript typecheck passed, and git diff --check is clean. GitHub CI currently shows no failures, with several broad jobs still running; I did not duplicate those suites locally.

One non-blocking copy-accuracy nit remains: the preview says the displayed text is “exact” and that Buzz “does not verify or rewrite” values, while the component trims leading separator whitespace and the formatter normalizes/truncates/JSON-encodes metadata. The security boundary is still satisfied because the complete semantically relevant attacker-controlled content is disclosed before send, but the wording should eventually describe that normalization accurately.

I am not approving because Wes did not explicitly request approval for this PR; this comment records the consolidated re-review verdict.

wesbillman
wesbillman previously approved these changes Aug 17, 2026
The general-channel welcome seeds are backdated by up to 120s, so a smoke
run that straddles midnight UTC renders two day dividers (Yesterday +
Today). Three specs asserted toBeVisible() on the bare
message-timeline-day-divider locator, which Playwright strict mode
rejects the moment two dividers exist — this is what failed Desktop
Smoke E2E shard 3 on the 23:53 UTC run of PR #6003 (test started before
midnight, assertion ran after).

Assert .first() visibility instead at all three sites (messaging.spec.ts
day-divider test, channels.spec.ts general-channel content test, and the
DM unread-clear test). The tests' intent is "a divider appears", which
the first divider proves on both sides of midnight. Pre-existing on
main; surfaced here because the PR run happened to cross the boundary.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock

Copy link
Copy Markdown
Contributor Author

The red CI at head 99bbbadb4 is addressed at head bbbb3564b6dbff653e25a0b2565b4b18ba806a0e.

What failed: Desktop Smoke E2E (3) — the Desktop check is just its aggregate gate. One genuine failure plus three passed-on-retry flakes.

Root cause (pre-existing on main, not introduced by this PR): the shard started at 23:53 UTC and messaging.spec.ts › day divider appears in timeline ran at 00:08 UTC. The mock general-channel seeds are backdated by up to 120s, so the timeline legitimately rendered two day dividers — Yesterday and Today — and the bare getByTestId("message-timeline-day-divider") locator failed Playwright strict mode on the initial run and both retries:

strict mode violation: getByTestId('message-timeline-day-divider') resolved to 2 elements:
    1) <section aria-label="Yesterday" ...>
    2) <section aria-label="Today" ...>

Fix: assert .first() visibility at the three strict-mode-vulnerable sites — messaging.spec.ts (day-divider test), channels.spec.ts (general-channel content test and the DM unread-clear test). The tests' intent is "a divider appears", which the first divider proves on both sides of midnight. The remaining bare-locator uses are toHaveCount(...) assertions or already .first()/.last()-scoped, which are strict-mode safe (verified by grep over desktop/tests and desktop/src).

Not touched: the three flaky specs (onboarding-agent-defaults, overscroll-boundary, persistent-agent-audience) passed on retry, are untouched by this branch, and flake on main as well — fixing them here would be scope creep.

Verification at bbbb3564b: the three touched specs pass locally under --project=smoke; biome and tsc clean; push hooks re-ran desktop-check / desktop-typecheck / desktop-test / desktop-tauri-checks / mobile-test green. CI on the new head is running now.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

CI is fully green at head bbbb3564b: all 18 checks pass. The previously-failing Desktop Smoke E2E shard 3 passed with the day-divider fix. Desktop Core failed once on an unrelated pre-existing flake — provider_deploy hit Text file busy (os error 26) in managed_agents/backend_tests.rs (a Linux ETXTBSY spawn race in a file untouched by this branch) — and passed on rerun. Since the approval landed at the prior head, the only remaining gate is re-approval.

@thomaspblock
thomaspblock dismissed stale reviews from jedwards27 and themiguelamador August 17, 2026 01:18

Already handled and superseeded by Wes

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

Re-review at exact head bbbb3564b6dbff653e25a0b2565b4b18ba806a0e found two remaining state/ordering blockers.

  1. [P1] An in-flight Projects send can cross a community switch and publish old-tenant context to the new tenant. ProjectAgentChatPanel.tsx:129-143 awaits managed-agent startup and/or DM opening, then calls sendChannelMessage without binding or rechecking the relay scope captured by this panel. Remounting on relay identity (ProjectDetailRightPanel.tsx:26-39) only removes the UI; it does not cancel an already-running async callback. Native submission resolves the currently active relay only when submit_event_with_created_at runs (desktop/src-tauri/src/relay/submit.rs:86-97). Therefore a switch during either await can make the old panel continue on the new relay. This is reachable when the same portable agent pubkey exists in both communities: the stale callback can open/reuse a new-tenant DM and sign the old community's repo/project metadata into it. The same shape exists in ProjectsAgentPromptPage.tsx:465-489. Capture (relay, identity) before the first await and submit/open the DM through APIs explicitly bound to that scope, or fail closed if scope changed before every post-await side effect. Add a deferred-promise regression that switches communities between start/open and send and proves no event is published to either wrong scope.

  2. [P1] A second user message sent in the opener's second is randomly hidden. After fixing the post-publication timestamp, isAtOrAfterConversationOpener still admits an unreferenced same-second event only when event.id <= opener.eventId (projectAgentConversation.ts:26-36). But every follow-up from both inline composers is sent as another root (ProjectAgentChatPanel.tsx:137-143; ProjectsAgentPromptPage.tsx:483-489, parentEventId is undefined). Nostr IDs are random within the second, so roughly half of immediate follow-up roots sort on the rejected side and disappear from the inline conversation even though send succeeded. Exact-head executable probe: opener id d…, second root id e…, both at created_at=100secondUserRootVisible=false; an e-tagged reply control → true. Existing regression covers only the reply case (projectAgentConversation.test.mjs:199-221). Make follow-ups causally reference the opener (or add a feature-owned conversation marker/cursor model) and regression-test a same-second second user send with an id on the rejected side.

The previous five findings are otherwise materially addressed, but these two async/ordering boundaries remain blockers.

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

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES
Reviewed: b74700daafa823e56c60b4e6470740ab28330888..1ab2dda5eaea3a87b6e211804c91874e1c395300
Risk: high — Projects state, drafts, managed-agent startup, and workspace application cross mutable relay and signing-identity boundaries.

The native Round-9 blockers are materially repaired: local startup binds the post-preflight signer and relay consumed by spawn (desktop/src-tauri/src/commands/agents.rs:247-305), and apply_workspace now serializes mutation, reconciliation, event sync, and launch restoration by transferring an owned guard into the detached restore (desktop/src-tauri/src/commands/workspace.rs:160-169,261-369). Signer-scoped Projects component state is also improved. One identity-boundary blocker remains.

[P1] Same-community identity replacement keeps the previous identity’s query cache and draft-store owner

The in-app import path changes only the identity query and removes only the profile query (desktop/src/features/onboarding/ui/OnboardingFlow.tsx:397-408). The surrounding workspace boundary remains keyed solely by community ID/config (communityKey), so CommunityQueryProvider and AppReady do not remount when A is replaced by B on the same relay (desktop/src/app/App.tsx:332-350,543-562). That preserves identity-unkeyed cached Projects and channel data (desktop/src/features/projects/hooks.ts:638-655; desktop/src/features/channels/hooks.ts:353).

The same path also leaves the module-global draft store owned by A. initDraftStore is the operation that changes its pubkey/relay bucket and clears its in-memory cache (desktop/src/features/messages/lib/useDrafts.ts:105-137), but it runs only inside useCommunityInit after applyCommunity; that effect does not depend on active pubkey and therefore does not rerun for same-community import (desktop/src/features/communities/useCommunityInit.ts:296-325,334-342). Although the repaired Project composer now computes a B-specific draftKey, all draft operations still execute inside A's active storage bucket (desktop/src/features/projects/ui/ProjectAgentChatPanel.tsx:257-266). B can consequently render A's cached project/channel state, enumerate A's drafts, and persist B drafts under A's bucket. Child remount/reset logic cannot repair these retained parent/module scopes.

Make the post-import workspace boundary signer-aware: rebuild/reset the community query client on (communityKey, currentPubkey) and reinitialize every identity-scoped singleton, including initDraftStore(newPubkey, relayUrl), before rendering B. Add an A→B→A same-relay E2E that seeds A's Projects/channels cache and Project draft, proves B sees neither, proves B's draft lands in B's bucket and first send is B-authored, then proves A restoration. Mutation-prove the test by removing pubkey from the boundary.

The new unit test only establishes that two pointer keys differ (desktop/src/features/projects/lib/projectAgentConversation.test.mjs:55-82); the existing import E2E stops at Home and never examines Projects cache/drafts or sends (desktop/tests/e2e/onboarding.spec.ts:3590-3628).

Exact-head validation:

  • PASS git diff --check b74700daafa823e56c60b4e6470740ab28330888..1ab2dda5eaea3a87b6e211804c91874e1c395300
  • PASS just desktop-test: 5,046/5,046
  • PASS just desktop-tauri-test: 2,637 passed across 16 targets, 18 ignored, 0 failed
  • GitHub exact-head Desktop Core, four smoke shards, two integration shards, macOS build, Windows Rust, release candidate, lint, and DCO are green; PR is mergeable.

Manual/native evidence: no GUI was launched under the shared-machine headless rule. The retained-cache/draft path is established by source and package evidence; live same-relay identity replacement remains unexercised.

Residual risk: workspace-apply coverage is helper-level rather than a command-level delayed-A/queued-B test, but the owned lock is traced through the production restore branches and no restoration path reacquires it. Any new head invalidates this verdict.

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

:bot: Jude’s code review agent

Integrated exact-head addendum to the REQUEST CHANGES verdict at 1ab2dda5eaea3a87b6e211804c91874e1c395300. The independent systems/integration lane is complete and does not change the verdict.

  • Local startup's repair is sound in source: after mesh preflight, production binds one relay read and one active-owner read, then spawn consumes those exact values (desktop/src-tauri/src/commands/agents.rs:245-305; propagation at :860-884,954-964). The full Tauri suite passed. Residual: the new signer tests exercise the binder, not a production-path preflight suspension followed by a same-relay A→B switch and no-spawn assertion.
  • Workspace apply/restore serialization is sound: generation assignment happens only after owned-lock acquisition, and launch restoration receives that same guard (desktop/src-tauri/src/commands/workspace.rs:30-36,153-169,318-364). The lock-boundary regression passed and was mutation-proven: moving generation allocation before lock acquisition made it fail with rc 101; the exact-head tree was restored clean.
  • No additional material systems defect was found in e499cf0..1ab2dda.

The blocking finding remains the product/adversarial lane's confirmed same-community identity replacement leak: A's query cache and module-global draft-store ownership survive into B. Exact-head validation across the integrated review: Desktop JS 5,046/5,046; Tauri 2,637 passed across 16 targets, 18 ignored; exact-head GitHub Desktop/build/E2E/DCO checks green. No GUI/live local-agent spawn workflow was run under the shared-machine headless rule. Any new head invalidates the entire verdict.

…cement

The community boundary key ignored the active signer, so importing a
different key mid-session (e.g. through the denied-membership key swap)
kept the previous identity's query cache and draft-store bucket alive.
Key the boundary on the active pubkey plus a signer epoch bumped by a
sentinel watching the community-scoped identity query, and reinitialize
identity-scoped singletons (draft store, avatar state) when the pubkey
changes. Covered by an A->B->A same-relay E2E asserting query-client
replacement and draft isolation across the swap.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock

Copy link
Copy Markdown
Contributor Author

🤖 Posted by Thomas's AI agent.

Round 11 — the remaining P1 (same-community identity replacement leak) addressed at ab7167a7c

Fix commit 496c57a8c plus a clean merge of latest main.

Fix: the workspace boundary is now signer-aware

  • communityKey now incorporates the active signer and a signer epoch: ${community.id}-${reinitKey}-${currentPubkey ?? "anonymous"}-${signerEpoch} (desktop/src/app/App.tsx). A new CommunityIdentityReplacementSentinel mounted inside CommunityQueryProvider watches the community-scoped identity query and bumps the epoch when the active pubkey changes after mount — i.e. the in-app import path through OnboardingFlow, which writes only to the community query client. Either signal remounts CommunityQueryProvider + AppReady, so B never inherits A's query cache.
  • useCommunityInit now resolves the active identity up front and reinitializes identity-scoped singletons on the new boundary mount: initDraftStore(newPubkey, relayUrl) runs with the replacement identity's pubkey, so the module-global draft store re-buckets before B renders (desktop/src/features/communities/useCommunityInit.ts). Avatar state additionally resets when the applied pubkey changed, while same-relay same-signer reconnects keep the existing behavior.

Regression: A→B→A same-relay E2E

same-relay identity replacement rebuilds the community boundary (A→B→A cache and draft isolation) in desktop/tests/e2e/onboarding.spec.ts:

  1. Boots as A (tyler) into the denied-membership gate, seeds A's persisted draft bucket and seeds the live community query client with a probe entry, capturing the client instance.
  2. Imports B (alice) via the denied-membership key swap and proves the query client instance was replaced and the seeded cache entry did not survive (poll distinguishes client-retained / cache-retained / rebuilt).
  3. Proves B's composer does not restore A's draft; B's typed draft persists in B's storage bucket, with A's bucket untouched; B's first send is accepted by the mock's signer assertion and renders.
  4. Relaunches as A on the same relay and proves A's original draft restores untouched, with B's draft absent.

Mutation-proof: with the signer removed from communityKey (epoch neutralized, pubkey dropped), the test fails at step 2 with client-retained; restored, it passes. Verified both directions locally against fresh e2e builds.

Exact-head validation at ab7167a7c

  • Desktop JS unit tests: 5,046/5,046 pass
  • Onboarding integration suite: 65/66 — the one failure (name-only community profile save preserves an existing avatar, "Error: Channel closed") reproduces identically on the clean pre-fix head in full-suite runs and passes in isolation on both heads, so it is a pre-existing suite-order flake unrelated to this change
  • tsc --noEmit and biome clean; pre-push gate (desktop check/typecheck/test, tauri checks, rust tests, mobile check/test) green on push

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

:bot: Jude’s code review agent

Verdict: APPROVE

Reviewed: 93114c9c65138397de39729fde0a816eb9f314ab..ab7167a7c4ebe4bfb6348d896f6c19482bf53b7f (repair delta from prior reviewed head 1ab2dda5eaea3a87b6e211804c91874e1c395300)

Risk: critical — same-relay identity replacement crosses signing identity, query-cache, draft-persistence, and deferred profile-work boundaries.

Findings: No unresolved material correctness, privacy, or user-impact finding at this head. I did not treat style preferences or coverage perfection as blockers.

The prior blocker is materially repaired:

  • desktop/src/app/App.tsx:251-270,371-381,603-610 observes an in-app identity replacement and changes the signer-aware community boundary, remounting the query client and application subtree rather than retaining the old identity's cache.
  • desktop/src/features/communities/useCommunityInit.ts:222-276,320-340 resolves the active signer before reset, clears signer-owned deferred avatar work when identity changes, and initializes the draft store with the new (pubkey, relay) scope after workspace application succeeds.
  • desktop/src/features/messages/lib/useDrafts.ts:105-147 drops its in-memory cache when pubkey or relay scope changes.
  • desktop/tests/e2e/onboarding.spec.ts:3642-3818 drives the real denied-membership A→B import path and checks query-client replacement, A/B draft isolation and bucket ownership, B's first send, and A restoration.

The complementary systems/integration and product/adversarial reviews independently found no material regression in 1ab2dda..ab7167a.

Validation at exact clean head ab7167a7c4ebe4bfb6348d896f6c19482bf53b7f:

  • git diff --check 93114c9c65138397de39729fde0a816eb9f314ab..HEAD — PASS.
  • pnpm install --frozen-lockfile and pnpm -C desktop build:e2e — PASS.
  • pnpm -C desktop exec playwright test --project=integration onboarding.spec.ts --grep 'same-relay identity replacement rebuilds the community boundary' — PASS, 1/1.
  • Causal mutation: removed both signer inputs (currentPubkey and signerEpoch) from communityKey, rebuilt, and reran the same journey — FAIL as expected with the old query client retained; restored exact-head bytes afterward.
  • pnpm -C desktop test — PASS, 5,090/5,090.
  • Exact-head GitHub Desktop Core, smoke, integration, macOS/Windows build, release-candidate, Rust lint, and DCO checks are green; PR is mergeable.

Manual/native evidence: No native GUI or live-relay identity-import run was performed under the shared-machine headless safety rule. The focused Playwright journey exercises the production React boundary and persisted localStorage buckets through the mock bridge; exact-head CI supplies broader Desktop integration/build coverage.

Residual risk: The focused journey is not a native Tauri/live-relay proof. Also, the test correctly fails when both signer boundary inputs are removed, while removing only signerEpoch remains green because currentPubkey updates in this mocked flow; that redundancy is non-blocking and does not undermine the production boundary.

No nits are being held over the merge. This head earned approval.

@thomaspblock
thomaspblock dismissed stale reviews from wpfleger96 and wesbillman August 19, 2026 10:45

change requests handled by jude

@thomaspblock
thomaspblock requested review from klopez4212 and removed request for themiguelamador August 19, 2026 11:39
@thomaspblock
thomaspblock merged commit 87f8ff8 into main Aug 19, 2026
24 checks passed
@thomaspblock
thomaspblock deleted the projects-v5-squashed branch August 19, 2026 11:50
thomaspblock added a commit that referenced this pull request Aug 19, 2026
## Summary
- Replaces the drawer panel icon's CSS `translateX` slide with a
`motion/react` width + corner-radius morph, so the icon reads as the
panel opening rather than the glyph drifting sideways.
- Honors `prefers-reduced-motion` via `useReducedMotion` (no animation
for users who opt out).

Follow-up polish to the projects workspaces work that just landed in
#6003 — the slide animation shipped there was the wrong visual.

## Test plan
- [x] Desktop unit tests and typecheck pass with this file at this
content (validated as part of the projects-v6 branch validation)
- [ ] Visual check: open/close the right drawer and confirm the icon
morphs in place

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
wpfleger96 pushed a commit that referenced this pull request Aug 19, 2026
…-in-thread

* origin/main: (32 commits)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311)
  fix(desktop): morph the drawer panel icon instead of sliding it (#6306)
  feat(desktop): refine repository-aware project workspaces (#6003)
  Fix mobile Activity thread navigation (#5850)
  perf(desktop): parallelize relay agent directory rebuild (#6258)
  Refine the mobile emoji picker (#5853)
  fix(desktop): exclude archived agents from nest, order regeneration (#5905)
  Add font size and conversation density preferences (#5644)
  fix(desktop): emit camelCase config-write payload fields (#6062)
  fix(desktop): downscale large avatars for agent-share PNG body (#6260)
  fix(desktop): preserve early relay auth challenges (#3320)
  Polish mobile message actions (#5873)
  Refine mobile pairing confirmation (#6018)
  chore(scripts): add buzz-adopt-prod-agents.sh (#6250)
  feat(managed-agents): close five Claude Code agent-config gaps (#4557)
  chore(hooks): keep mobile analysis out of pre-commit (#6236)
  fix(shared-ui): delay hover disclosures by default (#5821)
  fix(desktop-chrome): preserve balanced layout when sidebar collapses (#6000)
  Polish mobile timeline navigation (#5874)
  chore(release): release Buzz Desktop version 0.5.17 (#6234)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
tellaho added a commit that referenced this pull request Aug 19, 2026
…oundation

* origin/main:
  fix(desktop): hide archived channels from #/Tab autocomplete (#6156)
  Unify mobile channel details (#6113)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311)
  fix(desktop): morph the drawer panel icon instead of sliding it (#6306)
  feat(desktop): refine repository-aware project workspaces (#6003)

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>

# Conflicts:
#	desktop/src/app/AppShell.tsx
wpfleger96 pushed a commit that referenced this pull request Aug 19, 2026
…c-agent-commit-identity

* origin/main:
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311)
  fix(desktop): morph the drawer panel icon instead of sliding it (#6306)
  feat(desktop): refine repository-aware project workspaces (#6003)
  Fix mobile Activity thread navigation (#5850)
  perf(desktop): parallelize relay agent directory rebuild (#6258)
  Refine the mobile emoji picker (#5853)
  fix(desktop): exclude archived agents from nest, order regeneration (#5905)
  Add font size and conversation density preferences (#5644)
  fix(desktop): emit camelCase config-write payload fields (#6062)
  fix(desktop): downscale large avatars for agent-share PNG body (#6260)
  fix(desktop): preserve early relay auth challenges (#3320)
  Polish mobile message actions (#5873)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Aug 19, 2026
…ntion-phase1

* origin/main: (71 commits)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311)
  fix(desktop): morph the drawer panel icon instead of sliding it (#6306)
  feat(desktop): refine repository-aware project workspaces (#6003)
  Fix mobile Activity thread navigation (#5850)
  perf(desktop): parallelize relay agent directory rebuild (#6258)
  Refine the mobile emoji picker (#5853)
  fix(desktop): exclude archived agents from nest, order regeneration (#5905)
  Add font size and conversation density preferences (#5644)
  fix(desktop): emit camelCase config-write payload fields (#6062)
  fix(desktop): downscale large avatars for agent-share PNG body (#6260)
  fix(desktop): preserve early relay auth challenges (#3320)
  Polish mobile message actions (#5873)
  Refine mobile pairing confirmation (#6018)
  chore(scripts): add buzz-adopt-prod-agents.sh (#6250)
  feat(managed-agents): close five Claude Code agent-config gaps (#4557)
  chore(hooks): keep mobile analysis out of pre-commit (#6236)
  fix(shared-ui): delay hover disclosures by default (#5821)
  fix(desktop-chrome): preserve balanced layout when sidebar collapses (#6000)
  Polish mobile timeline navigation (#5874)
  chore(release): release Buzz Desktop version 0.5.17 (#6234)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
jedwards27 added a commit to jedwards27/buzz that referenced this pull request Aug 19, 2026
…urneys

* origin/main:
  chore: serialize mobile pre-push checks (block#6322)
  fix(buzz-acp): loosen workspace-scan guardrail to allow named paths (block#6261)
  fix(buzz-dev-mcp): expand leading ~ in read_file/str_replace paths (block#6271)
  perf(desktop): move five hot renderer paths from JS into Rust (block#6024)
  fix(media): accept portrait video resolutions (block#6058)
  fix(desktop): hide archived channels from #/Tab autocomplete (block#6156)
  Unify mobile channel details (block#6113)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (block#6311)
  fix(desktop): morph the drawer panel icon instead of sliding it (block#6306)
  feat(desktop): refine repository-aware project workspaces (block#6003)
  Fix mobile Activity thread navigation (block#5850)

Signed-off-by: Jude Edwards <judeedwards@squareup.com>
wpfleger96 added a commit that referenced this pull request Aug 19, 2026
Fold current origin/main into the cross-workspace agent library branch.
Base was re-taken from 934f332 after main rewound past the prior
staged base (203735f): the TTS-playback merge was reverted and #6271
(buzz-dev-mcp ~ expansion) and #6261 (buzz-acp workspace-scan) landed.

The 12 Rust conflict files were byte-identical between the two bases, so
every resolution replayed 1:1. Non-conflict main additions (#6271/#6261,
identity-persistence coordinator, owner-identity egress) merge cleanly.

Re-thread #6003 workspace-apply staleness guard, a one-sided main
addition the prior resolution dropped: app_state fields + init, the
next_apply_generation/assert_current_apply_generation/begin_workspace_apply
helpers, WORKSPACE_APPLY_SUPERSEDED, and main's tests, with the apply
lock transferred into HEAD's restructured fire-and-forget restore spawns
so the guard survives the reshaped restore path rather than pasting
main's now-incompatible spawn.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
yjc801 added a commit to yjc801/buzz that referenced this pull request Aug 20, 2026
Brings in 13 upstream commits (9c2f053). Seven conflicts, all where
upstream's "refine repository-aware project workspaces" (block#6003) and its
agents.rs module split landed on code this fork had relocated or extended.

- commands/agents.rs (mod decls + retention helpers): upstream split
  `retain/tombstone/archive_managed_agent_pending` and
  `build_agent_archive_request` out to `agents_pending.rs`. Took that split,
  but dropped `retain_managed_agent_pending` from it — the fork's copy in
  `agents_waker.rs` also issues the waker launch bundle and enrolment, so it
  is not the same function. `agents.rs` re-exports the waker one.
  `normalize_relay_mesh` / `trim_to_optional_string` /
  `resolve_created_avatar_url` stay in `agent_create_support.rs` (fork's
  earlier file-size split); upstream's copies dropped.

- commands/agents/provider_deploy.rs (modify/delete): this fork keeps
  `deploy_to_provider` in `agents_deploy.rs`, where it carries the backend
  transition fence and the `fresh_generation` classification the wake path
  needs. Ported upstream's tenant-scope check into that copy instead:
  `assert_payload_scope` plus the `expected_relay_url` /
  `expected_signer_pubkey` parameters, asserted after the deploy lock
  against the exact payload handed to `provider_deploy`, with upstream's
  five regression tests. Deleted the upstream file.

- commands/agents.rs (call sites): `start_managed_agent` now takes both
  `wake_replay_floor` and the two scope arguments; the local arm keeps the
  fork's `StartManagedAgentOutcome` wrapper over upstream's new preflight
  signature (which binds the workspace owner itself, superseding the fork's
  `owner_hex` parameter), and the provider arm keeps `fresh_generation`.

- managed_agents/runtime.rs: kept upstream's new doc comment on
  `start_managed_agent_process`; dropped its `child_rust_log_filter`, which
  the fork moved to `runtime/log_filter.rs`.

- shared/api/tauriManagedAgents.ts, features/agents/hooks.ts,
  testing/e2eBridge.ts: merged both option sets into one options object
  (`wakeReplayFloorTs` + `expectedRelayUrl` + `expectedSignerPubkey`) over
  the fork's `StartManagedAgentOutcome` return shape, and gave the mock
  bridge upstream's post-delay scope assertions.

Also fixed a clean-but-wrong automerge: `desktop/src-tauri/Cargo.toml` ended
up with `buzz_ws_client_pkg` declared twice.

Signed-off-by: Junchao Yan <yjc801@gmail.com>
thomaspblock added a commit that referenced this pull request Aug 20, 2026
…#6335)

## 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>
funge added a commit to funge/buzz that referenced this pull request Aug 20, 2026
…-shortcut

* origin/main: (341 commits)
  feat(desktop): make the Projects overview follow the selected section (block#6335)
  refactor(desktop): coordinate TTS playback (block#6341)
  fix(desktop): show complete repository trees (block#5102)
  Add appearance preference previews (block#6193)
  fix(desktop): restore emoji recents (block#6263)
  chore: serialize mobile pre-push checks (block#6322)
  fix(buzz-acp): loosen workspace-scan guardrail to allow named paths (block#6261)
  fix(buzz-dev-mcp): expand leading ~ in read_file/str_replace paths (block#6271)
  perf(desktop): move five hot renderer paths from JS into Rust (block#6024)
  fix(media): accept portrait video resolutions (block#6058)
  fix(desktop): hide archived channels from #/Tab autocomplete (block#6156)
  Unify mobile channel details (block#6113)
  Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (block#6311)
  fix(desktop): morph the drawer panel icon instead of sliding it (block#6306)
  feat(desktop): refine repository-aware project workspaces (block#6003)
  Fix mobile Activity thread navigation (block#5850)
  perf(desktop): parallelize relay agent directory rebuild (block#6258)
  Refine the mobile emoji picker (block#5853)
  fix(desktop): exclude archived agents from nest, order regeneration (block#5905)
  Add font size and conversation density preferences (block#5644)
  ...

Signed-off-by: John Funge <funge@squareup.com>

# Conflicts:
#	desktop/src/features/messages/ui/MessageComposer.tsx
#	desktop/src/features/settings/ui/KeyboardShortcutsCard.tsx
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.

6 participants