Skip to content

Projects v3: unify sharing, discussions, and issue ownership - #5792

Open
thomaspblock wants to merge 2 commits into
mainfrom
projects-design-v3-squashed
Open

Projects v3: unify sharing, discussions, and issue ownership#5792
thomaspblock wants to merge 2 commits into
mainfrom
projects-design-v3-squashed

Conversation

@thomaspblock

Copy link
Copy Markdown
Contributor

Summary

Projects v3 makes repository work shareable, discussion-aware, and easier to scan in one coherent workspace. People can copy canonical links, reopen the exact workspace tab, understand issue and pull-request context at a glance, find related channel conversations, and assign or unassign issues across Desktop and CLI.

  • Unified workspace — top-level sections sit above repository controls in one rounded workspace, with navigation positioned close to the page heading. README and Files retain branch selection; every section has a labeled icon header, and Issues and Pull Requests expose creation from a consistent right-aligned action.
  • Repository management — the repository selector is always available, including single-repository projects. Its integrated add flow lets project owners create a repository manually or select an existing repository without a separate toolbar button.
  • Readable work-item lists — issue and pull-request rows use plain-language context instead of opaque metadata. Files, commits, issues, pull requests, channels, and contributors share consistent row density and right-aligned timestamps, while deterministic fallback-avatar colors keep participants distinct on light backgrounds. Inbox pull-request metadata wraps between complete phrases and truncates long channel names instead of compressing copy into narrow columns.
  • Reliable entity links — projects, repositories, issues, pull requests, and commits have canonical buzz:// links, preview cards, OS deep-link routing, and tab-aware navigation. Reopening the same link re-applies its destination instead of leaving the user on a locally selected tab.
  • Related conversations — repository and work-item views surface channels discussing the current entity, including participants, channel navigation, message context, and an explicit notice when discovery reaches its 500-result cap.
  • Reversible issue ownership — trusted assignment and unassignment events work across Desktop, Tauri, buzz-sdk, and buzz issues. Assignees appear in project views and the assigned inbox, while authorized users can remove assignments directly from the assignee row.

Assignment state is derived chronologically from labeled Nostr notes. Issue authors and repository owners may change any assignee; other users may only assign or unassign themselves. Shared golden fixtures keep entity-link grammar and validation aligned across TypeScript and Rust.

The branch also updates webbrowser to the patched release for RUSTSEC-2026-0257.

Related issue

N/A.

Testing

  • just ci — formatting, lint, typechecking, unit tests, and builds passed
  • Full pre-push suite — organization, branch-skew, Desktop checks, typechecking, and tests passed on the latest push
  • cargo test -p buzz-cli and focused buzz-sdk assignment tests passed
  • Focused Tauri recipient-note and 500-result search-limit tests passed
  • Desktop entity-link and issue-assignment unit tests passed
  • Playwright smoke coverage passed for assignment, repeated entity-link navigation, repository create/select flows, section headers and actions, timestamp alignment, timeline icons, sentence-style issue/PR metadata, header spacing, avatar contrast, and Inbox metadata at stacked and side-rail breakpoints
  • Manual staging pass: link round-trips, Channels tab, assignment flows, and inbox routing

Screenshots

Pull requests explain who opened the request, where it lives, and which branch it comes from; fallback avatars remain visually distinct.

Pull request list with conversational metadata

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

Issue list with conversational metadata

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

Pull request Inbox detail with readable metadata

View the complete six-state Projects v3 screenshot set and the compact/wide Inbox comparison.


Supersedes #5624, whose head commit accumulated permanently-queued required check suites (block-dco-check et al.) that GitHub never dispatched. History flattened into a single signed-off commit on latest main; tree verified byte-identical (git merge-tree) to merging the original branch into main.

@thomaspblock
thomaspblock requested a review from a team as a code owner August 13, 2026 18:55
wesbillman pushed a commit that referenced this pull request Aug 13, 2026
…oxy (#5799)

**Category:** fix (CI)
**User Impact:** None — test-only change that unblocks `main` and every
open PR.

**Problem:** `main` has been red since #5629 landed on `45f4b91a3`:
`Desktop Smoke E2E (3)` fails `compact link preview image geometry
truncates long titles to one line` on every build (main run 31727837133,
and e.g. #5792, #5790). Two independently-green PRs raced: #5629 added
the test stubbing its preview image at the raw relay origin
(`http://localhost:3000/media/*.png`), while #5627 rewrites sent
snapshot media through the authenticated local media proxy
(`http://127.0.0.1:54321` in the E2E mock bridge). Merged together, the
image request goes to the proxy origin, the stub never matches, and
`naturalWidth` stays `0`.

**Solution:** Point the route stub at the mock proxy origin, matching
the existing `sent link preview media uses the authenticated proxy in
compact and rich cards` test in the same spec.

**Testing:** Reproduced the failure locally on `45f4b91a3`, then with
this fix: targeted test passes, and the full `messaging.spec.ts` smoke
suite passes 58/58.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Wintermute <3f1797424fd9ad6653a83665c660517777cd7f8c228c0d5907f49e01537f3ca5@buzz.block.builderlab.xyz>
@thomaspblock
thomaspblock enabled auto-merge (squash) August 13, 2026 20:47

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Combined review consolidated from two independent full passes, pinned to head aebe24ab1b626ad92cc91f526ded2185744eb9ef against merge-base 45f4b91.

The good news first: the #5624 cold-start deep-link blocker is fixed correctly — pending-entity queue with take/ack commands, get_current() coverage on Windows/Linux, launch-callback dedupe, and the pre-mount E2E regression we asked for. The provenance claim in the description also checks out: the first commit's tree is byte-identical to merging #5624's final head into latest main. CI is fully green at this head, and the desktop, Tauri workspace, CLI, SDK, and ACP suites were run clean locally at this exact commit during review.

Requesting changes on four items, detailed inline:

  1. Assignment state is silently lost once an assignment operation falls outside the bounded comment fetch window (projectIssues.mjs).
  2. A remote-agent add-repository failure after the project event publishes leaves an unrecoverable dangling project member (projectOwnerControl.ts / projectRepositoryCreation.ts / ACP publish path).
  3. The GHCR login retry in .github/workflows/docker.yml is unrelated to this PR and should be split out (with a precise read on what it actually does — it's not a broken duplicate).
  4. created_at future-cap asymmetry between the ACP and desktop owner-announcement paths can wedge an addressable head.

One minor note inline on entity deep-link queue poisoning, and one extraction suggestion: commit aebe24ab's messaging.spec.ts fix addresses a failure that's currently red on every main build — worth landing as a standalone PR so main goes green independent of this PR's iteration cycle.

* overriding authority while allowing a later observed owner/author decision
* to be superseded by the affected assignee.
*/
function assignmentStateForIssue(issue, issueCommentEvents) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 [IMPORTANT] assignmentStateForIssue() treats the fetched comment set as the complete assignment history, but both desktop fetch paths bound it: hooks.ts caps kind-1 notes at 500 per repo and projectWorkItems.ts at 2,000 shared across every loaded repo. Once newer comments push an older assignment operation out of that window, its assignee silently vanishes from the issue and from “Assigned to me” — and worse, a later self-service operation can omit the real prior head and get reduced with the wrong authority semantics.

Assignment/unassignment operations should be loaded independently and paginated to exhaustion (or snapshotted into a replaceable event) before reducing state. A regression test where the effective assignment predates 500+ unrelated repo comments would lock this in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4fd76d0. Added desktop/src/features/projects/assignmentOperationFetch.ts: fetchAssignmentOperationEvents paginates kind-1 notes filtered on #a + #t: [assignment, unassignment] to exhaustion (until-cursor + dedup by id), independent of the bounded comment window. Wired into both fetch paths — fetchProjectIssues (hooks.ts) and fetchProjectsWorkItems (projectWorkItems.ts). A fetch failure now surfaces as a failed section in the load notice instead of silently degrading assignment state. Verified every writer (desktop hook, Tauri managed-owner command, CLI/SDK builders) tags both #a and #t. 6 regression tests.

if (projectFrame.status === "ok" && projectFrame.events) {
finish({ events: projectFrame.events });
} else {
finish({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 [IMPORTANT] Partial-success events are discarded here on any non-ok status, but the ACP side publishes the announcements sequentially and can fail after the first event is already live — publish_project_owner_announcements in crates/buzz-acp/src/lib.rs even returns published_events alongside the error, and this handler throws them away.

Concretely for add-repository: if the repository event fails after the project event lands, the project head now references a repository event that doesn't exist, and retrying can't heal it — buildAddedRepositoryEventTemplatesFromHead() sees the coordinate already in the live head and throws (projectRepositoryCreation.ts “already contains” guard) before ever republishing the missing repository. That's an unrecoverable dangling project member.

Either consume the partial-success metadata and resume publication from where it stopped, or make retry detect “coordinate in head but repository head absent” and publish the missing event. Needs a test covering failure of event 2 after event 1 succeeds.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4fd76d0. buildAddedRepositoryEventTemplatesFromHead now takes repositoryHeadExists and returns resume templates when the live project head already references the coordinate but no kind-30617 repository head exists (the dangling-member state): it publishes only the missing repository event and reuses the live head as the project event, so no pointless created_at advance and the dominated-write guard is skipped (nothing writes the head in resume mode). projectOwnerControl.ts gains PartialAnnouncementPublishError + isDanglingProjectMemberPublish so the owner-agent path resumes in-flight partial failures too. Tests in projectRepositoryCreation.test.mjs and new projectOwnerControl.test.mjs.

Comment thread .github/workflows/docker.yml Outdated
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Retry GHCR login

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 [IMPORTANT — scope] This GHCR login retry has no connection to Projects v3, isn't mentioned in the PR description, and is an unexplained edit to a credential-bearing workflow inside an 88-file feature PR.

To be precise about what it actually does (so we're not overclaiming): the fork-PR guard holds — a skipped first step has outcome == 'skipped', not 'failure', so the retry can't fire on fork PRs, and the retry has no continue-on-error so a second failure still fails the job. It's a conventional one-retry sequence, not a broken duplicate. But it's applied to only 1 of the 4 GHCR login sites in this file, which undercuts the transient-failure motivation.

Please drop it from this PR and land it separately with rationale, applied consistently to all four login sites if it's needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dropped in 4fd76d0 — the docker.yml hunk is fully reverted (9 deletions). If the retry is still wanted, I'll propose it as a standalone PR with the correct rationale applied consistently to all login sites.

.collect::<Result<Vec<_>, _>>()?;
let mut builder = EventBuilder::new(Kind::Custom(input.kind), input.content).tags(nostr_tags);
if let Some(created_at) = input.created_at {
builder = builder.custom_created_at(Timestamp::from(created_at));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 [IMPORTANT] The ACP path (build_project_owner_announcement_events) rejects created_at > now + 300s, but this desktop command accepts any created_at unchecked. These are addressable events (30617/30621) where latest-created_at wins — an accidental far-future timestamp permanently wedges the project/repo head, since nothing can supersede it until that time. Owner-authorized either way, so it's a self-harm footgun rather than a privilege hole, but the asymmetry looks unintentional: other builders in this same file already clamp with created_at.max(Timestamp::now().as_secs()). Mirror the +300s cap (or the clamp) here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4fd76d0. The +300s future cap now lives in validate_project_owner_announcement (so both the command path and tests hit it), mirroring the ACP path exactly. Unit test project_owner_announcement_rejects_far_future_timestamps covers the boundary.

"take_pending_entity_deep_link",
);
if (!pending) break;
if (!onOpen(pending.href)) return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 [MINOR] If onOpen rejects the head of the queue (e.g. parseEntityLink fails on it), the drain returns without acknowledging — so every later link stays wedged behind the unparseable head forever. The shared golden fixture makes Rust/TS grammar drift unlikely, but ack-and-drop on parse failure would be cheap insurance against a permanently poisoned queue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4fd76d0. Unparseable queue heads are now acked and dropped in useEntityDeepLinks.ts, so a bad link can no longer wedge the links behind it. Unmount still leaves valid links queued for the next listener.

Squashed replacement for the projects-design-v3-layout-test branch
(supersedes PR #5624, whose head accumulated stuck queued check suites).

Projects v3 makes repository work shareable, discussion-aware, and
easier to scan in one coherent workspace:

- Unified workspace: top-level sections above repository controls in
  one rounded workspace, with labeled icon headers and consistent
  creation actions for Issues and Pull Requests.
- Repository management: always-available repository selector with an
  integrated add flow for creating or selecting repositories.
- Readable work-item lists: plain-language row context, consistent row
  density, right-aligned timestamps, deterministic fallback-avatar
  colors, and Inbox PR metadata that wraps between complete phrases.
- Reliable entity links: canonical buzz:// links, preview cards, OS
  deep-link routing, tab-aware navigation, and entity intents queued
  until the frontend acknowledges them so cold-start share links
  navigate reliably.
- Related conversations: repository and work-item views surface
  channels discussing the current entity, with participants, message
  context, and an explicit 500-result discovery cap notice.
- Reversible issue ownership: trusted assignment/unassignment events
  across Desktop, Tauri, buzz-sdk, and buzz issues, derived
  chronologically from labeled Nostr notes with owner/self-service
  authority rules.

Also updates webbrowser past RUSTSEC-2026-0257.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
thomaspblock added a commit that referenced this pull request Aug 14, 2026
…ish recovery, timestamp cap

Review items from #5792 (wpfleger96):

1. Drop the GHCR login retry from docker.yml — unrelated to Projects v3;
   will be proposed separately with rationale applied to all four login
   sites if still wanted.

2. Mirror the ACP path's +300s created_at cap in the desktop
   publish_project_owner_announcement command (moved into the
   validate function, with a unit test). These are addressable events
   where latest created_at wins; a far-future stamp would wedge the head.

3. Ack-and-drop unparseable entity deep links instead of leaving them
   queued, so one bad link can no longer wedge every later link behind
   the queue head. Unmount still leaves links queued for the next
   listener.

4. Make add-repository retry heal a dangling project member instead of
   throwing. The two-event publish can fail between the project head and
   the repository event; previously the "already contains" guard made
   that state unrecoverable. Now:
   - buildAddedRepositoryEventTemplatesFromHead accepts
     repositoryHeadExists and returns resume templates (publish only the
     missing repository event) when the live head references a
     coordinate with no repository head;
   - the owner-agent path consumes the ACP frame's partial-success
     events (PartialAnnouncementPublishError) and resumes in-flight;
   - the dominated-write guard no longer blocks the heal (resume
     publishes no project head, so nothing can be clobbered).
   Covered by tests for the retry-after-event-2-failure flow and the
   still-a-conflict case when a live repository head exists.

5. Load assignment/unassignment operations to exhaustion instead of
   trusting the bounded comment windows (500/repo in hooks.ts, 2,000
   shared in projectWorkItems.ts). A dedicated #t-filtered query
   paginates with an id-deduped until cursor and merges into the comment
   set before reduction; a failed query surfaces as a failed
   "assignments" section rather than silently dropping assignees.
   Regression test: an assignment older than 500+ unrelated comments
   still reduces to an assignee.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock
thomaspblock force-pushed the projects-design-v3-squashed branch from aebe24a to 4fd76d0 Compare August 14, 2026 00:00
…ish recovery, timestamp cap

Review items from #5792 (wpfleger96):

1. Drop the GHCR login retry from docker.yml — unrelated to Projects v3;
   will be proposed separately with rationale applied to all four login
   sites if still wanted.

2. Mirror the ACP path's +300s created_at cap in the desktop
   publish_project_owner_announcement command (moved into the
   validate function, with a unit test). These are addressable events
   where latest created_at wins; a far-future stamp would wedge the head.

3. Ack-and-drop unparseable entity deep links instead of leaving them
   queued, so one bad link can no longer wedge every later link behind
   the queue head. Unmount still leaves links queued for the next
   listener.

4. Make add-repository retry heal a dangling project member instead of
   throwing. The two-event publish can fail between the project head and
   the repository event; previously the "already contains" guard made
   that state unrecoverable. Now:
   - buildAddedRepositoryEventTemplatesFromHead accepts
     repositoryHeadExists and returns resume templates (publish only the
     missing repository event) when the live head references a
     coordinate with no repository head;
   - the owner-agent path consumes the ACP frame's partial-success
     events (PartialAnnouncementPublishError) and resumes in-flight;
   - the dominated-write guard no longer blocks the heal (resume
     publishes no project head, so nothing can be clobbered).
   Covered by tests for the retry-after-event-2-failure flow and the
   still-a-conflict case when a live repository head exists.

5. Load assignment/unassignment operations to exhaustion instead of
   trusting the bounded comment windows (500/repo in hooks.ts, 2,000
   shared in projectWorkItems.ts). A dedicated #t-filtered query
   paginates with an id-deduped until cursor and merges into the comment
   set before reduction; a failed query surfaces as a failed
   "assignments" section rather than silently dropping assignees.
   Regression test: an assignment older than 500+ unrelated comments
   still reduces to an assignee.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock
thomaspblock force-pushed the projects-design-v3-squashed branch from 4fd76d0 to 3198cc7 Compare August 14, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants