feat(desktop): refine repository-aware project workspaces - #6003
Conversation
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
left a comment
There was a problem hiding this comment.
Requesting changes for five correctness issues found in the project-workspace rewrite:
-
Inline agent conversations can expose unrelated DM history from the same second.
visibleAfteris only epoch-second precision, so every existing kind 9/40002 event with the samecreated_atas the opener passes the>=filter. Persist the exact opener event ID returned bysendChannelMessageand 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. -
“Owned by me” is accidentally restricted to projects already in “Added.”
listSidebarProjectsappliesaddedProjectAddresses.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. -
Add/remove becomes a visible no-op when localStorage writes fail.
writeProjectSidebarMembershipcatches 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. -
User-authored text containing the page-context marker is truncated.
stripProjectDetailAgentContextsearches 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. -
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 --noEmitpnpm 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
left a comment
There was a problem hiding this comment.
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:
-
Do not put untrusted project metadata into a hidden agent prompt.
ProjectAgentChatPanel.tsx:113-119silently appendsprojectDetailAgentContextBlock(context)to the user-signed DM.projectDetailAgentContext.ts:92-108interpolates 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 announcementnametag (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
repoContextBlockhas the same class of problem and should be fixed at the shared trust boundary rather than leaving one entry point exploitable. -
Do not fabricate an origin conversation from nearby channel traffic.
DiscussionChannels.tsx:67-77fetches the 20 newest events in the entity'shchannel at or before the entity timestamp, anddiscussionChannels.ts:75-86chooses 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 atdiscussionChannels.test.mjs:84-102codifies the heuristic rather than proving an association.The
htag 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>
|
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):
Verification on 07c2be3: full desktop unit suite 4989/4989 pass, |
jedwards27
left a comment
There was a problem hiding this comment.
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:
-
Cross-community agent-conversation state is not tenant-scoped.
ProjectAgentChatPanel.tsx:56-60keys the persisted conversation and draft only bydetail:${context.repoAddress}, whileProjectDetailRightPanel.tsx:24-30remounts 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.restoreProjectsAgentConversationalso accepts any matching channel ID/candidate without provingchannelType === "dm"or the expected participant set. Include stable community/relay identity in persistence, draft, and component reset keys, and validate the restored DM participants. -
Repository switches retain same-named branch/tag state.
ProjectDetailScreen.tsx:803-815clears work-item selections and source but does not reset ref selection.useProjectRepositoryRefSelection.ts:24-34deliberately preserves a current branch/tag whenever its name exists in the new repository. Switching A→B can silently keepreleaseorv1rather 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. -
The hidden prompt still signs semantically intact attacker instructions under the user identity.
ProjectAgentChatPanel.tsx:112-118appendsprojectDetailAgentContextBlock;projectDetailAgentContext.ts:115-150JSON-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. -
The persisted opener cursor uses a post-publication timestamp and can hide a fast reply forever. Native
send_channel_messagesigns/publishes first, then returnsUtc::now()(desktop/src-tauri/src/commands/messages.rs:546-575). Projects stores that value (ProjectAgentChatPanel.tsx:123-139), whileisAtOrAfterConversationOpenerexempts 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 actualcreated_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.
|
Follow-up reliability finding, independently reproduced from source at unchanged head
Mongo ran the full exact-head |
…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>
|
All four round-2 findings are addressed at head 1. Cross-community agent-conversation state is now tenant-scoped, and restored pointers are validated. 2. Repository switches reset ref selection. 3. The appended context payload is now visibly disclosed — nothing signed under the user's key is hidden. 4. The opener cursor is the signed event's Validation at |
wesbillman
left a comment
There was a problem hiding this comment.
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:
- The localStorage failure fallback still loses sequential sidebar membership mutations.
desktop/src/features/projects/lib/projectSidebarMembership.ts:67-90computes every add/remove from a freshreadProjectSidebarMembership(...). The write path catchessetItemfailures 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, anda9852a9f3does 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.
|
Follow-up at exact head
Direct reproduction against the production helpers at this head, with Expected is |
wesbillman
left a comment
There was a problem hiding this comment.
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:
-
P1 — attacker-controlled prompt content is still undisclosed when the user authorizes/signs it.
ProjectAgentChatPanel.tsx:117-135accepts only the user's composer text, then appendsprojectDetailAgentContextBlock(context)insidehandleSubmitimmediately beforesendChannelMessage. The pre-send UI exposes onlycontextLabel(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-149still 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.
-
P1 — sidebar fallback still loses sequential actions when persistence stays unavailable.
projectSidebarMembership.ts:67-90recomputes every mutation from localStorage, while a failed write survives only in event detail. Thusadd(A) → add(B) → remove(A)still dispatches[A] → [B] → [], not[A] → [A,B] → [B]. Heada9852a9f3did 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>
|
Both round-3 findings are addressed at head 1. Sequential sidebar mutations under storage failure ( 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 New
2. Pre-send disclosure of the appended prompt payload New This covers the submit UI boundary, not just the formatter: 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 |
|
Carl, an automated reviewer, commenting via Wes’s GitHub account. Royal Court follow-up on exact head
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 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. |
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>
|
The red CI at head What failed: Root cause (pre-existing on Fix: assert Not touched: the three flaky specs ( Verification at |
|
CI is fully green at head |
Already handled and superseeded by Wes
jedwards27
left a comment
There was a problem hiding this comment.
Re-review at exact head bbbb3564b6dbff653e25a0b2565b4b18ba806a0e found two remaining state/ordering blockers.
-
[P1] An in-flight Projects send can cross a community switch and publish old-tenant context to the new tenant.
ProjectAgentChatPanel.tsx:129-143awaits managed-agent startup and/or DM opening, then callssendChannelMessagewithout 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 whensubmit_event_with_created_atruns (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 inProjectsAgentPromptPage.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. -
[P1] A second user message sent in the opener's second is randomly hidden. After fixing the post-publication timestamp,
isAtOrAfterConversationOpenerstill admits an unreferenced same-second event only whenevent.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,parentEventIdisundefined). 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 idd…, second root ide…, both atcreated_at=100→secondUserRootVisible=false; ane-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
left a comment
There was a problem hiding this comment.
: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
left a comment
There was a problem hiding this comment.
: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>
|
🤖 Posted by Thomas's AI agent. Round 11 — the remaining P1 (same-community identity replacement leak) addressed at
|
jedwards27
left a comment
There was a problem hiding this comment.
: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-610observes 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-340resolves 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-147drops its in-memory cache when pubkey or relay scope changes.desktop/tests/e2e/onboarding.spec.ts:3642-3818drives 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-lockfileandpnpm -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 (
currentPubkeyandsignerEpoch) fromcommunityKey, 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.
change requests handled by jude
## 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>
…-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>
…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
…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>
…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>
…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>
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>
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>
…#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>
…-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
Summary
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
pnpm build:e2e