Skip to content

feat(projects): add agent and CLI project-home support - #6590

Merged
thomaspblock merged 30 commits into
mainfrom
projects-channel-first-pt1-agent-cli
Aug 26, 2026
Merged

feat(projects): add agent and CLI project-home support#6590
thomaspblock merged 30 commits into
mainfrom
projects-channel-first-pt1-agent-cli

Conversation

@thomaspblock

Copy link
Copy Markdown
Contributor

Summary

  • inject bounded project-home identity and repository context into managed agent sessions
  • add project-aware CLI flows for creating projects, repositories, issues, and related channels
  • match project homes through the existing relay query surface, then filter channel metadata client-side

This is Part 1 of the channel-first Projects stack. Part 2 contains project creation and model foundations.

Testing

  • cargo fmt --all -- --check
  • cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings
  • cargo test -p buzz-cli -p buzz-acp — 1,184 tests passed, 1 doc test ignored
  • full pre-push gate passed

Post-Deploy Monitoring & Validation

  • validate project-home agent context and project-aware CLI commands against a staging relay
  • healthy signals: project context matches the active channel, explicit repo coordinates remain stable, and normal channels receive no project block
  • failure signals: cross-channel project context, duplicate project creation, or commands targeting an unrelated repository; mitigate by reverting this PR

Give agents bounded project-home context and project-aware CLI operations while keeping channel matching client-filtered through the existing relay query surface.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock
thomaspblock requested a review from a team as a code owner August 23, 2026 01:02
@thomaspblock
thomaspblock marked this pull request as draft August 23, 2026 03:53

@thomaspblock thomaspblock left a comment

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.

Cassandra adversarial/security review — needs work

The red Unit Tests job is not caused by this diff: it fails linking untouched buzz-voice with could not find native static library 'sherpa-onnx-c-api'. That check should be retried rather than patched in this projects PR.

I found two source-level blockers independently while tracing the new project-home resolution.

P1 — Any relay writer can hijack a channel's agent project context and redirect channel-scoped issues (confidence 100)

Evidence

  • crates/buzz-acp/src/prompt_project.rs:23-25: !event_is_unlisted(event) && event_has_tag_value(event, "buzz-channel", channel_id)
  • crates/buzz-acp/src/prompt_project.rs:27-33: the matching events are ordered only by created_at, then the first parseable event wins.
  • crates/buzz-cli/src/commands/project_channel.rs:27-31: let project = pick_oldest_listed(&projects); followed by if let Some(member) = first_member_repo(event) { return Ok(member); }
  • docs/nips/NIP-MP.md:139: `buzz-channel` on a project is **metadata only**.
  • docs/nips/NIP-MP.md:188: The relay MUST NOT check whether the signer owns, maintains, or has any relationship to a member repository.

Trigger scenario

  1. An attacker who knows a project channel UUID publishes a listed kind:30621 carrying that buzz-channel and an a tag for the attacker's repository. This is protocol-valid and requires no authority over the channel.
  2. The attacker gives it an earlier accepted timestamp than the legitimate project (or simply publishes before project creation).
  3. ACP selects that event as the channel's project home and promotes its name/owner/repository into generated [Context] instructions.
  4. buzz issues create --channel <victim-channel> independently makes the same oldest-event choice and returns the attacker's first member coordinate without checking that the project signer controls the channel or that the member repo is actually bound to it.
  5. A normal “create a task in this project” request is therefore signed against an unrelated attacker-chosen repository.

This crosses an integrity boundary: unauthenticated project metadata is being treated as authoritative routing configuration. Resolve the project from an authenticated channel-owned binding/type, or require a verifiable relationship between the selected project signer and channel authority. At minimum, channel-scoped repo resolution must verify the selected 30617 is bound to the requested channel and reject ambiguous projects rather than choosing oldest.

P1 — Global slug squatting lets any signer block another user's project creation (confidence 100)

Evidence

  • crates/buzz-cli/src/commands/projects.rs:373-379: other_listed_project(&fetch_projects_by_dtag(client, slug).await?, &caller_pubkey) causes a conflict when any other pubkey has the slug.
  • docs/nips/NIP-MP.md:134: Only the signer can replace their (pubkey, 30621, d) coordinate.
  • docs/nips/NIP-MP.md:194: newest created_at wins per (pubkey, 30621, d), and one pubkey can never overwrite another's coordinate.

Trigger scenario

An attacker publishes listed projects for common slugs (app, website, a known upcoming product name). Every later buzz projects create <slug> by every other identity is rejected locally, even though the protocol intentionally namespaces projects by signer. The suggested error action (“Add a repository to that project instead”) cannot work because editing is signer-only. Do not impose relay-wide uniqueness on an owner-namespaced coordinate; duplicate-card prevention needs an authority-scoped rule.

Additional adversarial risk retained in this PR comment

crates/buzz-cli/src/commands/project_channel.rs:178-185 adds the selected foreign project owner as a maintainers tag on an implicitly created caller-owned repository. Under docs/nips/NIP-MP.md:215-217, that tag is sufficient claim authority for the foreign signer. I did not live-test Desktop's resulting fold, but this should be removed or explicitly justified before merge; untrusted project metadata must not grant provenance/claim authority over a newly created repo.

Coverage: full 12-file diff read; traced ACP project lookup → generated context, CLI channel lookup → issue creation, implicit repo creation, project collision checks, NIP-MP authority and claim semantics. I did not mutate the branch or run a live hostile relay reproduction.

Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>

@thomaspblock thomaspblock left a comment

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.

Cassandra re-review of 7a9af2ac — one routing blocker remains

The original two P1 findings are fixed in the authoritative-selection path: foreign channel/project claims no longer route ACP or CLI, ambiguity fails closed, cross-signer slug/channel squatting is removed, and implicit repo creation no longer grants foreign maintainers authority.

P1 — Existing same-id repository bypasses the new channel-binding check (confidence 100)

Evidence

  • crates/buzz-cli/src/commands/project_channel.rs:181-188:
    if let Some(existing) =
        crate::commands::repos::fetch_own_repo_announcement(client, &repo_id).await?
    {
        let _ = try_add_own_repo_to_channel_project(client, channel, &repo_id).await;
        return Ok(ChannelProjectRepo {
            repo_owner: existing.pubkey.to_hex(),
            repo_id,
        });
    }
  • The new binding check exists in repo_from_announcement at lines 94-104, but this fallback does not call it.

Trigger scenario

  1. The caller already owns repo 30617:<caller>:app, bound to channel A (or unbound).
  2. They own a repository-empty project home with slug app in channel B.
  3. buzz issues create --channel B finds no authoritative project/member and no caller-owned repo bound to B, then reaches ensure_default_repo.
  4. fetch_own_repo_announcement("app") returns the channel-A repository. The code attaches it to the channel-B project and returns it without checking or rebinding its buzz-channel.
  5. The issue is silently created against channel A's unrelated repository. Subsequent calls repeat the same misrouting, while ACP correctly refuses to recognize that member as authoritative for B.

The fallback must apply the same first-buzz-channel equality invariant before returning. If an existing same-id repo is bound elsewhere, fail with an actionable conflict or choose a non-colliding id; do not attach or route to it.

Advisory — maintainer authorization reads only the first value (confidence 75)

Evidence

  • crates/buzz-cli/src/commands/project_channel.rs:88-91:
    || repo.tags.iter().any(|tag| {
        matches!(tag.as_slice(), [name, value, ..]
            if name == "maintainers" && value.eq_ignore_ascii_case(&signer))
    })
  • crates/buzz-acp/src/prompt_project.rs:93-101 likewise returns only tag.get(1) for each maintainers tag.
  • VISION_PROJECTS.md:27 and NIP-34 model maintainers as a multi-value tag; Desktop deliberately reads all values (desktop/src/features/projects/projectModels.ts:283-285).

A valid ['maintainers', first, project_signer] repository authorizes the signer in Desktop but is rejected by both new routing implementations. Iterate all values after the tag name so ACP, CLI, and Desktop share one authority rule.

Re-review coverage: exact fix diff a6c5f1db..7a9af2ac; traced authoritative selection, ambiguity, project creation collisions, implicit repo fallback, and maintainer parsing. Report-only; no branch mutation.

Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>

@thomaspblock thomaspblock left a comment

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.

Cassandra final security/authority re-review — findings cleared at 7bbed3f1

No remaining security or adversarial findings in the incremental fix.

Verified:

  • crates/buzz-cli/src/commands/project_channel.rs:197-205 now calls require_repo_channel_binding before reusing or attaching a same-slug existing repository, so a repository bound to channel A cannot route a channel-B issue.
  • require_repo_channel_binding uses the first buzz-channel value, matching the relay's fail-closed binding semantics, and rejects both mismatched and absent bindings.
  • ACP's multi_tag_values and CLI's tag.as_slice()[1..] now inspect every pubkey value in every maintainers tag, matching NIP-34/Desktop semantics.
  • Regressions cover the mismatched existing binding and authorization by a later maintainer value.
  • The prior fixes remain intact: project-home selection requires a channel-bound live member repository plus signer authority; ambiguity fails closed; cross-signer slug/channel squatting is absent; implicit creation does not grant foreign maintainer authority.

Verdict for my security/authority lane: merge-ready at exact head 7bbed3f127f25559fc301044842ee6582b2fdc9a. CI and independent correctness review are outside this verdict and were still in progress when checked.

@thomaspblock
thomaspblock marked this pull request as ready for review August 23, 2026 12:02
@thomaspblock
thomaspblock enabled auto-merge (squash) August 23, 2026 21:48
thomaspblock and others added 3 commits August 23, 2026 17:48
## Summary
- create explicit NIP-MP projects with a home channel and default
repository
- preserve standalone repository folding, project deletion, and
deterministic repository selection
- restore Template, Team, visibility, and agent settings in the project
creation flow

This is Part 2 of the channel-first Projects stack, following #6590. It
is independently based on `main`; Part 3 adds the project-home channel
surface.

## Testing
- focused project collection, creation, channel, and model tests: 38/38
passed
- Desktop unit suite: 5,415/5,415 passed
- TypeScript, Biome, and differential file-size checks passed
- full pre-push gate passed

## Post-Deploy Monitoring & Validation
- create listed and unlisted projects with and without templates in the
first staging Desktop session
- healthy signals: one home channel, one default repository, stable
project coordinates, and no duplicate legacy card
- failure signals: partial project creation, duplicate projects, missing
default repository, or stale sidebar entries; mitigate by reverting this
PR

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
## Summary
- classify and render project-home channels through the shared channel
glyph and lifecycle helpers
- let the normal channel pane host a project idle auxiliary surface and
focus drawer
- align channel management, headers, member bars, and empty-channel
actions with project channel semantics

This is Part 3 of the channel-first Projects stack, based on #6591. Part
4 adds the project-home navigation and context experience.

## Testing
- focused channel lifecycle, pane helper, and project-home channel
tests: 7/7 passed
- Desktop unit suite: 5,422/5,422 passed
- E2E-mode Desktop build passed
- TypeScript, Biome, and differential file-size checks passed
- full pre-push gate passed

## Post-Deploy Monitoring & Validation
- open normal, temporary, private, and project-home channels in the
first staging Desktop session
- healthy signals: normal channels retain their existing composer/thread
behavior and project homes use the project glyph and auxiliary slot
- failure signals: missing composer, incorrect channel kind, stuck focus
drawer, or project chrome on a normal channel; mitigate by reverting
this PR

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
## Summary
- render an explicit project's home channel through the normal channel
timeline and composer
- add a resizable project context rail with codebase, channel, people,
and workspace navigation
- keep project agent conversations bounded to the project home and
preserve repository/detail routes

This is Part 4 of the channel-first Projects stack, based on #6594. The
final part contains overview and workspace completion polish.

## Testing
- focused project conversation, route, summary, workspace-sheet, and
related-channel tests: 39/39 passed
- Desktop unit suite: 5,439/5,439 passed
- E2E-mode Desktop build passed
- TypeScript, Biome, and differential file-size checks passed
- full pre-push gate passed

## Post-Deploy Monitoring & Validation
- open project homes from project and channel entry points in the first
staging Desktop session
- healthy signals: one channel timeline/composer, stable repository
context, bounded project agent history, and reversible workspace sheets
- failure signals: duplicate channel surfaces, stale repository
selection, unrelated DM history, or sheets replacing the channel route;
mitigate by reverting this PR

Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Comment on lines +251 to +256
fn truncate_repo_name(name: &str) -> String {
if name.len() <= 128 {
return name.to_string();
}
name.chars().take(128).collect()
}

@matt2e matt2e Aug 24, 2026

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.

The guard measures bytes but the truncation takes chars, while build_repo_announcement rejects names over 128 bytes. A multibyte project name over 128 bytes still exceeds the byte limit after chars().take(128), so default-repo creation errors instead of truncating (e.g. a 100-CJK-character name). Same pattern in projects.rs ensure_default_create_repo, which has no byte check at all — truncate on a byte budget at a char boundary, as the prompt-side truncation does.
🤖

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 2e0fe69: both default-repository paths now share UTF-8-safe truncation on a 128-byte budget, with a CJK regression test.

Comment on lines +178 to +191
const workspaceSheet =
workspaceSheetOpen && workspaceSheetTab && workspaceRepository ? (
<ProjectHomeWorkspaceSheet
key={`${workspaceSheetTab}:${workspaceRepository.id}`}
identityPubkey={identityQuery.data?.pubkey}
onOpenCommit={handleOpenCommit}
onRepositoryAdded={handleFilesAdded}
onSelectRepository={setWorkspaceRepositoryId}
project={project}
projects={projects}
repository={workspaceRepository}
tab={workspaceSheetTab}
/>
) : null;

@matt2e matt2e Aug 24, 2026

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.

workspaceSheet is a fresh JSX element every render and flows into the memoized ChannelPane as idleAuxiliaryPanel, so while the sheet is open any parent render (query cache updates, local state) defeats React.memo(ChannelPane) and re-renders the whole message timeline behind the drawer — the exact unstable-prop gotcha the repo docs call out. Its inputs are all stable callbacks/ids, so wrapping the construction in React.useMemo restores the memo boundary.
🤖

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 2e0fe69: the conditional workspace sheet element is memoized with its complete dependency set, preserving the downstream ChannelPane memo boundary.

Comment on lines +158 to +170
if (homeChannel) {
const alreadyMember = homeChannel.memberPubkeys.some(
(pubkey) =>
normalizePubkey(pubkey) === normalizePubkey(selectedAgent.pubkey),
);
if (!alreadyMember) {
await addChannelMembers({
channelId: homeChannel.id,
pubkeys: [selectedAgent.pubkey],
role: "bot",
});
}
}

@matt2e matt2e Aug 24, 2026

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.

The bot member-add is gated on homeChannel being set, not on the message actually targeting it. restoreProjectsAgentConversation can restore a 1:1 DM while homeChannelId is set, and submitProjectAgentMessage then sends to the DM — in that case this block silently adds the agent as a bot member of the project home channel as a side effect of a DM follow-up. Guard on the resolved target, e.g. only add when !conversation || conversation.channel.id === homeChannel.id.
🤖

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 2e0fe69: bot membership is now added only when the resolved existing conversation is the project home channel (or no conversation exists yet).

matt2e
matt2e previously approved these changes Aug 24, 2026
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>

@thomaspblock thomaspblock left a comment

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.

Cassandra security re-review of 2e0fe6999 — one tenant-scope blocker remains

Matt's three reported defects are correctly fixed: UTF-8 names now truncate to a 128-byte prefix at a character boundary in both callers, the CJK regression passes, the workspace-sheet element has a complete useMemo dependency set, and an existing DM no longer triggers project-home membership.

P1 — Project-home membership is not bound to the captured relay/signer scope (confidence 75)

Evidence

  • desktop/src/features/projects/ui/ProjectAgentChatPanel.tsx:167-171:
    await addChannelMembers({
      channelId: homeChannel.id,
      pubkeys: [selectedAgent.pubkey],
      role: "bot",
    });
  • The immediately following agent start/open/send path passes the captured relayScope and signer at ProjectAgentChatPanel.tsx:183-201, but this membership mutation passes neither.
  • desktop/src/shared/api/types.ts:88-92 exposes no expected relay/signer fields on AddChannelMembersInput.
  • desktop/src-tauri/src/commands/channels.rs:533-559 accepts only channel/pubkeys/role and calls unscoped submit_event(builder, &state).
  • desktop/src-tauri/src/relay/submit.rs:71-77 resolves the currently active relay and signing keys when called.

Trigger scenario

  1. The panel captures project home channel A, relay A, and signer A.
  2. The user submits while a community or identity switch races the Tauri membership command (or the switch occurs after this unscoped await starts).
  3. add_channel_members resolves the then-active workspace and signs/publishes the captured channel UUID there; if that UUID exists in relay B, the bot membership is mutated in the wrong tenant. Even without a collision, the wrong-relay failure occurs outside the later fail-closed path.
  4. submitProjectAgentMessage then checks expectedRelayUrl / expectedSignerPubkey and fails closed, leaving membership as a partial side effect even though no project message was sent.

This contradicts the nearby invariant that “every relay side effect” is scope-bound. Extend the membership command/API with expected relay and signer parameters and perform the same assert/captured-target submission used by the message path, or move the membership operation into a scoped orchestration boundary. The channel-target guard fixes Matt's DM case but not this tenant race.

Verification: reviewed exact incremental diff 7d6c4abce..2e0fe6999; git diff --check passed; independently ran the new CJK test at exact head (1 passed). Report-only; no branch mutation.

Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra security/adversarial re-review — adff0acff7f5fbe61795aad6e3a5670e1300b45d

Verdict: merge-ready from my lane. No actionable findings.

I reviewed the full 2e0fe6999..adff0acff diff before tracing the surrounding Projects submit path, Tauri command boundary, relay scope helpers, explicit-key submission helper, workspace state snapshots, and mock bridge. The remaining tenant-scope blocker is closed:

  • ProjectAgentChatPanel.tsx:168-175 passes the callback-captured relay and signer scopes into project-home membership.
  • channels.rs:546-552 resolves one relay base and one signable key snapshot, then validates both captured scopes before mutation.
  • channels.rs:572 submits with submit_event_at_with_keys(builder, &state, &relay_base, &signing_keys), so neither relay nor signer is re-read after validation.
  • e2eBridge.ts:7419-7426 applies both checks after the injected delay, matching the race shape rather than checking too early.

Adversarial scenarios checked: switch before relay resolution; identity swap between relay and key reads; switch after validation; malformed/empty optional scopes; multi-member partial failure; restored-DM guard interaction; membership failure before message send; and mismatch behavior in the mock bridge. The fixed snapshot either fails closed before publication or publishes only with the captured relay/key pair.

Independent verification at exact HEAD:

  • Desktop full test suite: 5,451 passed.
  • Tauri relay::scope::tests: 11 passed.
  • git diff --check: clean.

Residual/testing gap (recorded here durably): I did not exercise a live relay-backed community switch during an in-flight membership request. The production call path and deterministic delayed bridge cover the relevant ordering, and I do not consider this merge-blocking.

@thomaspblock
thomaspblock requested review from jedwards27 and removed request for jedwards27 August 24, 2026 13:34

@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 — request changes

Reviewed base e23632941331502c0330e51d407e667bea26ef57 through exact head adff0acff7f5fbe61795aad6e3a5670e1300b45d against VISION.md, VISION_PROJECTS.md, TESTING.md, NIP-MP authority semantics, the ACP resolver/cache, CLI project/repository routing, relay query execution, and the changed Desktop project journeys.

P1 — reject a same-slug repository bound outside the new project home

crates/buzz-cli/src/commands/projects.rs:372-380 adds the coordinate returned by ensure_default_create_repo to the project and publishes it. But ensure_default_create_repo at projects.rs:665-670 returns any caller-owned same-ID repository without checking its buzz-channel. This omits the binding invariant already enforced for issue-time reuse at crates/buzz-cli/src/commands/project_channel.rs:171-182,197-205.

If the caller already owns repo app bound to channel A (or with no binding), buzz projects create app --channel B reports success and publishes a channel-B project containing the channel-A repo. ACP correctly refuses to treat that member as authoritative for B, and later channel-scoped issue routing conflicts rather than targeting the advertised project. Apply the same binding check before reuse (or fail before project publication), with a regression for mismatched and absent bindings.

P1 — do not permanently cache project absence or mutable project metadata

crates/buzz-acp/src/pool.rs:598-611 caches Option<PromptProjectInfo> indefinitely, including None; fetch_project_home_for_channel explicitly treats empty as final at pool.rs:2986-2989. There is no TTL, relevant-event invalidation, or session-boundary refresh.

If ACP resolves channel C before its project/repository publication completes, it caches None. Creating the project later cannot add the Project block to any later turn/session in that process until restart. Positive entries likewise retain obsolete project names/default repositories. Use bounded freshness or invalidate on relevant project/repository events, and regress None → project resolution without restarting ACP.

P1 — do not treat a truncated global query page as authoritative absence

The project-home paths issue one-shot 1,000-row queries: ACP at crates/buzz-acp/src/pool.rs:2993-3003, CLI projects at crates/buzz-cli/src/commands/projects.rs:74-86, and CLI repositories at crates/buzz-cli/src/commands/project_channel.rs:160-168. The relay clamps the SQL query to 1,000 at crates/buzz-relay/src/handlers/req.rs:957-960, while non-single-letter custom-tag matching occurs only after that limited read at crates/buzz-relay/src/api/bridge.rs:1308-1315; the SQL tag pushdown at req.rs:1001-1044 covers #p/#d, not #buzz-channel.

Once more than 1,000 newer visible heads exist, an older authoritative project or repository can be excluded by unrelated global rows. ACP then resolves (and permanently caches) no project; CLI channel routing can say the channel is not a project home or take fallback behavior. BuzzClient already exposes composite-cursor pagination at crates/buzz-cli/src/client.rs:683-729. Page to a defined exhaustive/bounded result with explicit truncation failure, or add indexed relay-side support; a full page cannot prove absence. Add coverage that places the authoritative head beyond page one.

Validation and residual risk

  • Clean exact-head cargo test -p buzz-cli -p buzz-acp passed (809 + 9 + 374 tests; one doc test ignored); clippy for both packages passed with -D warnings.
  • Desktop unit suite passed 5,451/5,451; Desktop check/typecheck passed; five targeted create/open/retry/lost-ack/sidebar project journeys passed.
  • Keyboard Enter/Space and aria-pressed behavior, a 900×720 viewport at 24px root text, control visibility, and horizontal overflow were probed successfully in the browser artifact. No additional source-level product/accessibility blocker was found.
  • All applicable exact-head GitHub checks are green. Those checks do not exercise the three failure shapes above.
  • Residual product risk: no exact-head native Tauri/WebView journey or native receipt was available for the materially changed navigation/layout, so native focus, OS input, and shell resizing remain unproven.
  • The 1,001-head starvation case was established from the client/relay control flow, not reproduced against a seeded live relay.

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Gauge correctness/testing verification of Jude's three P1s — all confirmed at exact head adff0acff7f5fbe61795aad6e3a5670e1300b45d

Independent source verification, formed before reading other responders. All three mechanisms are real; none is speculative. For each, the answer to "which test would fail if this were wrong?" is currently none — that is itself the coverage finding.

1. ensure_default_create_repo reuses a same-slug repo with no binding check — confirmed, confidence 100

crates/buzz-cli/src/commands/projects.rs:665-670:

    let repo_id = repo_id_from_project_slug(slug)?;
    if fetch_own_repo_announcement(client, &repo_id)
        .await?
        .is_some()
    {
        return Ok(repo_id);
    }

The issue-time path already enforces the invariant this skips — crates/buzz-cli/src/commands/project_channel.rs:197-200:

    if let Some(existing) =
        crate::commands::repos::fetch_own_repo_announcement(client, &repo_id).await?
    {
        require_repo_channel_binding(&existing, channel)?;

So buzz projects create app --channel B publishes a channel-B project whose member repo is bound to channel A (or unbound), and the divergence surfaces later as an ACP/CLI routing conflict rather than at creation time. Required regression (must fail on today's code): create-with-existing-repo where the repo's first buzz-channel (a) mismatches → Conflict before project publication; (b) is absent → Conflict; (c) matches → reuse succeeds.

2. Permanent caching of None and of mutable project metadata — confirmed, confidence 100

crates/buzz-acp/src/pool.rs:598-610 (lookup_project) returns any cached entry — including a cached None (cache.get(&channel_id).cloned() yields Some(None)return cached;) — and inserts fetched.clone() unconditionally with no TTL. The projects map (pool.rs:547) is a separate Arc<RwLock<HashMap>> with no invalidation path: invalidate_channel / invalidate_channel_sessions operate on session state only and never touch it. The fetch helper documents absence as final: pool.rs:2988 — "Empty results are not retried: most channels are not project homes." Consequence: resolve channel before project creation → Project block unavailable until process restart; renames/default-repo changes similarly frozen. Required regression: None resolved → project published → subsequent turn (same process) carries the Project block.

3. Truncated 1,000-row page treated as authoritative absence — confirmed, confidence 100

  • ACP fetch_project_home_for_channel, pool.rs:2993-3003: "kinds": [KIND_PROJECT], "limit": 1000no tag filter at all; channel matching is client-side, so the page is all listed projects relay-wide.
  • CLI fetch_projects_for_channel, crates/buzz-cli/src/commands/projects.rs:74-86: same shape, client-side project_tags_match_channel post-filter.
  • CLI fetch_channel_repos, project_channel.rs:161-166: uses #buzz-channel, but the relay pushes only #p/#d into SQL (crates/buzz-relay/src/handlers/req.rs:1001-1044); custom-tag matching happens after the LIMIT-clamped read (req.rs:957-960, api/bridge.rs:1308-1315).

Past 1,000 newer visible heads, an older authoritative project/repo silently drops out; the caller concludes "not a project home" or takes default-repo fallback. Compounding interaction worth naming: a starvation-induced false absence from (3) is then cached permanently by (2) — a transient load condition becomes a sticky wrong answer for the process lifetime. Required regression: authoritative head seeded beyond page one → resolver either finds it (pagination; BuzzClient composite cursor exists at crates/buzz-cli/src/client.rs:683-729) or fails explicitly on truncation — never a silent absence.

Verified clean / scope

I re-derived each claim from the head sources quoted above; I did not re-run the suites (Jude's exact-head runs and the green check-runs at adff0ac stand). Negative claims here are scoped to the files and line ranges cited. Wrench owns the fixes; I will re-review the incremental diff when it lands, gating on the three regressions above failing-before/passing-after.

— Gauge (correctness/testing/reliability lane)

@thomaspblock

Copy link
Copy Markdown
Contributor Author

Cassandra security/adversarial response to Jude's review — exact head adff0acff7f5fbe61795aad6e3a5670e1300b45d

Verdict: all three requested changes are valid; the current head needs work. I formed this assessment from the full diff and surrounding call paths before reading the other creature reports.

  1. Same-slug repository reuse breaks the channel authority invariant — confidence 100.

    crates/buzz-cli/src/commands/projects.rs:665-670 currently says:

        if fetch_own_repo_announcement(client, &repo_id)
            .await?
            .is_some()
        {
            return Ok(repo_id);
        }

    This returns an existing repository without applying the require_repo_channel_binding check already used by issue-time routing. A repository bound to channel A, or with no binding, can therefore be published as channel B's default member and leave project creation reporting success while ACP later rejects the project home. The creation path must fail before project publication unless the first buzz-channel binding equals the requested channel.

  2. Permanent positive/negative ACP caching makes mutable authority metadata stale — confidence 100.

    crates/buzz-acp/src/pool.rs:604-610 currently says:

        if let Some(cached) = self
            .projects
            .read()
            .ok()
            .and_then(|cache| cache.get(&channel_id).cloned())
        {
            return cached;
        }

    Because the map stores Option<PromptProjectInfo>, this permanently returns cached absence as well as stale positive metadata. Resolve-before-create therefore suppresses the Project block until process restart; later repository/default changes are also invisible. Bounded freshness or relevant-event invalidation is required, including a None -> project regression without restart.

  3. A one-shot, post-filtered page cannot establish absence — confidence 100.

    crates/buzz-relay/src/api/bridge.rs:1308-1315 currently applies the complete filter only after the limited database read:

                for se in stored_events {
                    if !event_in_accessible_channel(&se, &accessible_channels) {
                        continue;
                    }
                    if !buzz_core::filter::filters_match(std::slice::from_ref(filter), &se) {
                        continue;
                    }

    #buzz-channel is not pushed into that SQL read at the reviewed head. More than 1,000 newer nonmatching heads can hide the older authoritative repository/project and produce false absence.

    Implementation constraint: ordinary response-length pagination does not fix the custom-tag case by itself. If a 500-row underlying DB page post-filters to zero matches, a client that treats page.len() < 500 as EOF stops immediately and never advances to the older candidate rows. The fix must either push the exact buzz-channel predicate into SQL before LIMIT, or expose an underlying-scan cursor/exhaustion signal independent of filtered response length. Any exhaustive scan must also have a defined bound and fail explicitly when that bound is exceeded; otherwise attacker-controlled relay history becomes unbounded prompt-path memory/time work.

I reviewed only exact pushed head adff0acff; the implementation work is still in progress and is not covered by this verdict. I will re-review the exact pushed revision, including bound behavior and the starvation regression, when it lands.

Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
@thomaspblock

Copy link
Copy Markdown
Contributor Author

Merged current main (22cdda441d19da1b15dd7db4d932d2f067cd87b3) and resolved the paired-tag framing conflict at exact head 495f1d97b95c15b19c104acf4167ef17d046af2a (plan: PLANS/PR6590_ROUND11_MERGE_MAIN_FRAMING.md).

  • Preserved both prompt_project and main's prompt_framing modules.
  • Kept project-home context fields inside main's <context> semantic section.
  • Updated the remaining base-prompt [Context] reference to <context>.
  • No round-10 TTL behavior changed.

Exact-head validation:

  • cargo test -p buzz-acp: 820 unit + 9 lifecycle tests passed; doc tests passed.
  • Desktop unit suite: 5,651 passed.
  • Desktop typecheck passed.
  • just ci passed.
  • Pre-push repository gates passed (Rust, Desktop tests/typecheck, Tauri, file-size, branch skew).
  • git diff --check passed; worktree clean.

GitHub now reports the PR mergeable; required CI is running. Please re-review both prior ACP boundaries at this new merge head because the main merge changes the ACP diff.

@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: 22cdda441d19da1b15dd7db4d932d2f067cd87b3..495f1d97b95c15b19c104acf4167ef17d046af2a (exact head 495f1d97b95c15b19c104acf4167ef17d046af2a)

The prior owner-review blocker is fixed, and the changed-head systems/merge review found no remaining author-actionable defect.

Prior blocker cleared

ProjectChannelRequestDialog.tsx:41-49 now shows a semantic Lifetime field whenever ttlSeconds is present, including the formatted requested duration and the consequence that the channel “Cleans up automatically after that period of inactivity.” Approval forwards the same request.request.ttlSeconds unchanged (useProjectChannelRequests.ts:150-159), so reviewed and applied values agree. The no-TTL path omits this copy.

The dialog uses native description-list relationships and shared Radix AlertDialog title/description, focus, action, and cancel semantics. Pending settlement disables both actions and keeps the dialog open appropriately.

Systems and merge result

Project authority still resolves once before ACP session creation or configured initial_message. Indeterminate authority returns the typed local outcome, preserves/requeues the batch, retains the healthy ACP process/session, and does not mutate crash-circuit or respawn state. The same resolved context drives prompt framing. Review of the merged-main delta found no unexpected authority/session regression; the effective database delta is the intended custom-tag SQL pushdown, activated only for the supported one-string #buzz-channel filter before LIMIT.

Exact-head evidence

  • Desktop tests: 5,651/5,651 passed; typecheck passed.
  • TTL regression: 2/2 passed; deleting the disclosure made the positive test fail as intended, then restoration returned it to green.
  • cargo test -p buzz-acp: 820 unit + 9 lifecycle tests passed.
  • cargo test -p buzz-cli: 385 passed; one doc test ignored.
  • git diff --check passed; clean trees and exact live head were rechecked by both lanes.
  • All exact-head GitHub checks are terminal successful or expected skipped.

Native Tauri keyboard/screen-reader observation was not run. Static semantics and deterministic coverage show no material defect; native observation remains a reviewer/tooling confidence gap, not author action.

Author action: none.
Verification owner: CI for its completed exact-head gates; reviewer/tooling for any optional native accessibility observation.

@thomaspblock
thomaspblock merged commit de188eb into main Aug 26, 2026
32 checks passed
@thomaspblock
thomaspblock deleted the projects-channel-first-pt1-agent-cli branch August 26, 2026 19:09
wpfleger96 added a commit that referenced this pull request Aug 26, 2026
Resolve types.ts conflict: keep the canvasTypes re-export refactor from
this branch; #6590 only stripped adjacent blank lines and its
AddChannelMembersInput additions land outside the conflict.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Aug 26, 2026
…c-agent-commit-identity

* origin/main:
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)

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

* origin/main:
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  Use paired tags for standing & per-turn context (#6701)
  fix(cli): preserve signatures in event reads (#6884)
  refactor(db): finish replaceable event store extraction (#6777)
  Fix Admin feedback filter overflow (#6825)
  fix(desktop): stop pulsing addressed agents on send (#6873)
  fix(desktop): prioritize sidebar channel status (#6861)
  feat(desktop): hyperlink selected composer text on link paste (#6684)
  chore(release): release Buzz Desktop version 0.5.20 (#6839)

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

* origin/main:
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
tulsi-builder added a commit that referenced this pull request Aug 26, 2026
#6900)

**Category:** fix
**User Impact:** Sidebar mention numerals now match the user's
theme/accent color and appear for mentions inside threads, not just
top-level channel messages. Hover-to-preview on channel rows is
unchanged.

**Problem:** Follow-ups to #6696:

1. The mention numeral used a fixed red `--notification` token that
ignored the user's theme and accent selection.
2. The numeral read `appBadgeCount`, which deliberately excludes
threaded replies (Home's badge subtotal counts those toward the Dock
badge). Mentions inside threads therefore never surfaced a numeral on
the channel row — only the generic thread-activity dot.

**Solution:**

1. The badge now reuses the primary accent pair (`bg-primary` /
`text-primary-foreground`), the same treatment as the DM count badge, so
it follows whatever accent the user applies — including the pinned
neutral accent of the Buzz themes. The now-unused `--notification`
tokens and Tailwind utilities are removed.
2. Non-DM rows now count every unread mention/broadcast — threaded or
top-level — via a new `highPriorityCount` channel projection, replacing
the boolean `highPriorityUnread` across the TS contract, the Tauri
SQLite store, the e2e mock bridge, and the native test rig. The Dock/app
badge projection (`appBadgeCount`) is unchanged, so Dock totals do not
double-count thread activity that Home already counts.

The hover-to-preview channel activity popover is untouched; e2e coverage
now pins the combination (thread mention → numeral, no dot, popover
still opens and lists the mentioning reply).

<details>
<summary>File changes</summary>

**desktop/src/features/channels/useUnreadChannels.ts**
Non-DM sidebar counts switch from `appBadgeCount` to the
mention/broadcast-inclusive `highPriorityCount`; the high-priority
channel set derives from the same count.

**desktop/src/features/sidebar/ui/SidebarSection.tsx**
Badge drops the fixed notification classes and inherits the
accent-following default.

**desktop/src/shared/styles/globals/theme.css /
desktop/tailwind.config.js**
Remove the unused fixed `--notification` tokens and semantic utilities.

**desktop/src/shared/api/tauriObservedUnread.ts /
desktop/src-tauri/src/observed_unread.rs**
Projection contract: `highPriorityUnread: bool` → `highPriorityCount:
u64`, aggregated per unread high-priority event.

**desktop/src/testing/e2eBridge.ts /
desktop/src/features/channels/observedUnreadNativeRig.mjs**
Mock/native-rig projections mirror the new count field.

**desktop/src/features/channels/observedUnreadNative.test.mjs**
Native badge-lane test seeds a high-priority event to match the
numeral's new semantics.

**desktop/tests/e2e/badge.spec.ts**
Top-level mention test now asserts the badge equals the applied accent
(seeded `#22c55e` under an adversarial theme). New test: in-thread
mention shows the numeral, no dot, and the hover preview popover still
lists the mentioning reply.

**desktop/tests/e2e/channel-activity-popover.spec.ts /
desktop/tests/e2e/thread-unread.spec.ts**
Seeded fixtures that mention the user now expect the numeral (which
subsumes the dot).

</details>

## Reproduction steps

1. Pick any theme + accent color in Settings → Appearance.
2. Receive a top-level message mentioning you in an inactive channel →
the row shows a numeral pill in your accent color.
3. Receive a reply **inside a thread** that mentions you in an inactive
channel → the row shows the numeral (previously only a dot).
4. Hover the row → the channel activity preview still opens and lists
the mentioning reply.
5. Switch accents/themes → the pill re-colors accordingly; the Dock
badge count is unchanged from before.

## Verification

- `tsc --noEmit`, Biome, `check:px-text` — clean
- Desktop unit tests: 179/179 channel-suite; full suite has one
pre-existing failure from #6590 (`useRetainedProjectGitViews.test.mjs`
loader error, fails on clean `main`)
- Tauri Rust: `observed_unread` tests + clippy + fmt — clean
- Playwright smoke: badge (19), channel-activity-popover (10),
thread-unread (13), plus channels/inbox/stream/messaging/mentions suites
— all green

## Screenshots

Screenshots are posted in the PR discussion using immutable
repository-hosted image URLs.

Signed-off-by: tulsi <tulsi@block.xyz>
salman1993 added a commit that referenced this pull request Aug 26, 2026
…cp-sessions

* origin/main:
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  Use paired tags for standing & per-turn context (#6701)
  fix(cli): preserve signatures in event reads (#6884)
  refactor(db): finish replaceable event store extraction (#6777)
  Fix Admin feedback filter overflow (#6825)

Signed-off-by: Salman Mohammed <smohammed@squareup.com>

# Conflicts:
#	crates/buzz-acp/src/pool.rs
mfethe1 added a commit to mfethe1/buzz that referenced this pull request Aug 26, 2026
…-home + block#6842 sidebar unread-DM overflow)

# Conflicts:
#	desktop/src/shared/api/types.ts
Bartok9 pushed a commit to Bartok9/buzz that referenced this pull request Aug 26, 2026
block#6900)

**Category:** fix
**User Impact:** Sidebar mention numerals now match the user's
theme/accent color and appear for mentions inside threads, not just
top-level channel messages. Hover-to-preview on channel rows is
unchanged.

**Problem:** Follow-ups to block#6696:

1. The mention numeral used a fixed red `--notification` token that
ignored the user's theme and accent selection.
2. The numeral read `appBadgeCount`, which deliberately excludes
threaded replies (Home's badge subtotal counts those toward the Dock
badge). Mentions inside threads therefore never surfaced a numeral on
the channel row — only the generic thread-activity dot.

**Solution:**

1. The badge now reuses the primary accent pair (`bg-primary` /
`text-primary-foreground`), the same treatment as the DM count badge, so
it follows whatever accent the user applies — including the pinned
neutral accent of the Buzz themes. The now-unused `--notification`
tokens and Tailwind utilities are removed.
2. Non-DM rows now count every unread mention/broadcast — threaded or
top-level — via a new `highPriorityCount` channel projection, replacing
the boolean `highPriorityUnread` across the TS contract, the Tauri
SQLite store, the e2e mock bridge, and the native test rig. The Dock/app
badge projection (`appBadgeCount`) is unchanged, so Dock totals do not
double-count thread activity that Home already counts.

The hover-to-preview channel activity popover is untouched; e2e coverage
now pins the combination (thread mention → numeral, no dot, popover
still opens and lists the mentioning reply).

<details>
<summary>File changes</summary>

**desktop/src/features/channels/useUnreadChannels.ts**
Non-DM sidebar counts switch from `appBadgeCount` to the
mention/broadcast-inclusive `highPriorityCount`; the high-priority
channel set derives from the same count.

**desktop/src/features/sidebar/ui/SidebarSection.tsx**
Badge drops the fixed notification classes and inherits the
accent-following default.

**desktop/src/shared/styles/globals/theme.css /
desktop/tailwind.config.js**
Remove the unused fixed `--notification` tokens and semantic utilities.

**desktop/src/shared/api/tauriObservedUnread.ts /
desktop/src-tauri/src/observed_unread.rs**
Projection contract: `highPriorityUnread: bool` → `highPriorityCount:
u64`, aggregated per unread high-priority event.

**desktop/src/testing/e2eBridge.ts /
desktop/src/features/channels/observedUnreadNativeRig.mjs**
Mock/native-rig projections mirror the new count field.

**desktop/src/features/channels/observedUnreadNative.test.mjs**
Native badge-lane test seeds a high-priority event to match the
numeral's new semantics.

**desktop/tests/e2e/badge.spec.ts**
Top-level mention test now asserts the badge equals the applied accent
(seeded `#22c55e` under an adversarial theme). New test: in-thread
mention shows the numeral, no dot, and the hover preview popover still
lists the mentioning reply.

**desktop/tests/e2e/channel-activity-popover.spec.ts /
desktop/tests/e2e/thread-unread.spec.ts**
Seeded fixtures that mention the user now expect the numeral (which
subsumes the dot).

</details>

## Reproduction steps

1. Pick any theme + accent color in Settings → Appearance.
2. Receive a top-level message mentioning you in an inactive channel →
the row shows a numeral pill in your accent color.
3. Receive a reply **inside a thread** that mentions you in an inactive
channel → the row shows the numeral (previously only a dot).
4. Hover the row → the channel activity preview still opens and lists
the mentioning reply.
5. Switch accents/themes → the pill re-colors accordingly; the Dock
badge count is unchanged from before.

## Verification

- `tsc --noEmit`, Biome, `check:px-text` — clean
- Desktop unit tests: 179/179 channel-suite; full suite has one
pre-existing failure from block#6590 (`useRetainedProjectGitViews.test.mjs`
loader error, fails on clean `main`)
- Tauri Rust: `observed_unread` tests + clippy + fmt — clean
- Playwright smoke: badge (19), channel-activity-popover (10),
thread-unread (13), plus channels/inbox/stream/messaging/mentions suites
— all green

## Screenshots

Screenshots are posted in the PR discussion using immutable
repository-hosted image URLs.

Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: Bartok9 <259807879+Bartok9@users.noreply.github.com>
Bartok9 pushed a commit to Bartok9/buzz that referenced this pull request Aug 26, 2026
block#6900)

**Category:** fix
**User Impact:** Sidebar mention numerals now match the user's
theme/accent color and appear for mentions inside threads, not just
top-level channel messages. Hover-to-preview on channel rows is
unchanged.

**Problem:** Follow-ups to block#6696:

1. The mention numeral used a fixed red `--notification` token that
ignored the user's theme and accent selection.
2. The numeral read `appBadgeCount`, which deliberately excludes
threaded replies (Home's badge subtotal counts those toward the Dock
badge). Mentions inside threads therefore never surfaced a numeral on
the channel row — only the generic thread-activity dot.

**Solution:**

1. The badge now reuses the primary accent pair (`bg-primary` /
`text-primary-foreground`), the same treatment as the DM count badge, so
it follows whatever accent the user applies — including the pinned
neutral accent of the Buzz themes. The now-unused `--notification`
tokens and Tailwind utilities are removed.
2. Non-DM rows now count every unread mention/broadcast — threaded or
top-level — via a new `highPriorityCount` channel projection, replacing
the boolean `highPriorityUnread` across the TS contract, the Tauri
SQLite store, the e2e mock bridge, and the native test rig. The Dock/app
badge projection (`appBadgeCount`) is unchanged, so Dock totals do not
double-count thread activity that Home already counts.

The hover-to-preview channel activity popover is untouched; e2e coverage
now pins the combination (thread mention → numeral, no dot, popover
still opens and lists the mentioning reply).

<details>
<summary>File changes</summary>

**desktop/src/features/channels/useUnreadChannels.ts**
Non-DM sidebar counts switch from `appBadgeCount` to the
mention/broadcast-inclusive `highPriorityCount`; the high-priority
channel set derives from the same count.

**desktop/src/features/sidebar/ui/SidebarSection.tsx**
Badge drops the fixed notification classes and inherits the
accent-following default.

**desktop/src/shared/styles/globals/theme.css /
desktop/tailwind.config.js**
Remove the unused fixed `--notification` tokens and semantic utilities.

**desktop/src/shared/api/tauriObservedUnread.ts /
desktop/src-tauri/src/observed_unread.rs**
Projection contract: `highPriorityUnread: bool` → `highPriorityCount:
u64`, aggregated per unread high-priority event.

**desktop/src/testing/e2eBridge.ts /
desktop/src/features/channels/observedUnreadNativeRig.mjs**
Mock/native-rig projections mirror the new count field.

**desktop/src/features/channels/observedUnreadNative.test.mjs**
Native badge-lane test seeds a high-priority event to match the
numeral's new semantics.

**desktop/tests/e2e/badge.spec.ts**
Top-level mention test now asserts the badge equals the applied accent
(seeded `#22c55e` under an adversarial theme). New test: in-thread
mention shows the numeral, no dot, and the hover preview popover still
lists the mentioning reply.

**desktop/tests/e2e/channel-activity-popover.spec.ts /
desktop/tests/e2e/thread-unread.spec.ts**
Seeded fixtures that mention the user now expect the numeral (which
subsumes the dot).

</details>

## Reproduction steps

1. Pick any theme + accent color in Settings → Appearance.
2. Receive a top-level message mentioning you in an inactive channel →
the row shows a numeral pill in your accent color.
3. Receive a reply **inside a thread** that mentions you in an inactive
channel → the row shows the numeral (previously only a dot).
4. Hover the row → the channel activity preview still opens and lists
the mentioning reply.
5. Switch accents/themes → the pill re-colors accordingly; the Dock
badge count is unchanged from before.

## Verification

- `tsc --noEmit`, Biome, `check:px-text` — clean
- Desktop unit tests: 179/179 channel-suite; full suite has one
pre-existing failure from block#6590 (`useRetainedProjectGitViews.test.mjs`
loader error, fails on clean `main`)
- Tauri Rust: `observed_unread` tests + clippy + fmt — clean
- Playwright smoke: badge (19), channel-activity-popover (10),
thread-unread (13), plus channels/inbox/stream/messaging/mentions suites
— all green

## Screenshots

Screenshots are posted in the PR discussion using immutable
repository-hosted image URLs.

Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: Bartok9 <259807879+Bartok9@users.noreply.github.com>
brow added a commit that referenced this pull request Aug 26, 2026
…ifications-pr

* origin/main:
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  Use paired tags for standing & per-turn context (#6701)
  fix(cli): preserve signatures in event reads (#6884)
  refactor(db): finish replaceable event store extraction (#6777)
  Fix Admin feedback filter overflow (#6825)
  fix(desktop): stop pulsing addressed agents on send (#6873)
  fix(desktop): prioritize sidebar channel status (#6861)
  feat(desktop): hyperlink selected composer text on link paste (#6684)

Signed-off-by: Tom Brow <tomb@block.xyz>
baxen added a commit that referenced this pull request Aug 26, 2026
Reconciling slice A against current main took main's `mode="wait"` on the
`AnimatePresence` that hosts the auxiliary surfaces, added by #6590 to
serialize replacements. That was the wrong side to take: overlapping mount is
load-bearing for the cover drawer.

Replacing one cover surface with another must stay dismissable with a single
Escape, and the guards that achieve it — `CoverDrawer`'s capture-phase claim
standing down while exiting, and the exiting panel declining to
`preventDefault` — only have something to coordinate while both surfaces are
mounted at once. Under `mode="wait"` the outgoing drawer unmounts before the
incoming one enters, the press lands on nothing, and the user needs a second
Escape.

`agent-activity-cover.spec.ts:349` asserts the overlap directly and failed
deterministically (3/3 CI retries, reproduced locally); removing the attribute
turns it green. Verified no regression in the behaviour #6590 was protecting:
the drawer, focus-mode, thread and projects e2e specs pass (124 tests), and
the comment now records why the attribute must not come back.

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz>
santhiprakash added a commit to santhiprakash/buzz that referenced this pull request Aug 27, 2026
The Add Channel dialog rendered every row with a hardcoded '#' prefix, ignoring channel.visibility and channel.channelType. The sidebar already shows a lock for private channels and a file icon for forum channels; the channel browser drifted away from that and the same channel looked different in two surfaces of the same window (block#6120).

Add a pure, unit-testable ChannelRowIcon component with getChannelRowIconKind() and use it in ChannelBrowserDialog. The sidebar now uses ChannelGlyph (introduced by block#6590), so this PR focuses on the browser surface.

Tests cover the helper directly and render the component under JSDOM to assert the right lucide-* class reaches the DOM for each channel kind.

Signed-off-by: Santhi Prakash <b.santhiprakash@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Aug 27, 2026
…r-contracts

* origin/main: (26 commits)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  Use paired tags for standing & per-turn context (#6701)
  fix(cli): preserve signatures in event reads (#6884)
  refactor(db): finish replaceable event store extraction (#6777)
  Fix Admin feedback filter overflow (#6825)
  fix(desktop): stop pulsing addressed agents on send (#6873)
  fix(desktop): prioritize sidebar channel status (#6861)
  feat(desktop): hyperlink selected composer text on link paste (#6684)
  ...

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

* origin/main: (138 commits)
  fix(client): resurface hidden DMs from live activity (#6885)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  Use paired tags for standing & per-turn context (#6701)
  fix(cli): preserve signatures in event reads (#6884)
  refactor(db): finish replaceable event store extraction (#6777)
  Fix Admin feedback filter overflow (#6825)
  fix(desktop): stop pulsing addressed agents on send (#6873)
  fix(desktop): prioritize sidebar channel status (#6861)
  ...

# Conflicts:
#	Justfile
TheSentinel454 added a commit that referenced this pull request Aug 27, 2026
…at-vacuum

* origin/main:
  fix(projects): allow owners to delete agent projects (#6533)
  Fade expanded video controls on hover (#6926)
  fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
  fix(client): resurface hidden DMs from live activity (#6885)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
wpfleger96 pushed a commit that referenced this pull request Aug 27, 2026
…agent-edit

* origin/main: (39 commits)
  chore(deps): update dependency vitest to v4.1.11 (#6667)
  chore(deps): update dependency @tanstack/react-virtual to v3.14.10 (#6666)
  chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
  fix(projects): allow owners to delete agent projects (#6533)
  Fade expanded video controls on hover (#6926)
  fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
  fix(client): resurface hidden DMs from live activity (#6885)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  ...

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

* origin/main: (145 commits)
  chore(deps): update rui314/setup-mold digest to 7e4f20a (#6663)
  chore(deps): update dependency vitest to v4.1.11 (#6667)
  chore(deps): update dependency @tanstack/react-virtual to v3.14.10 (#6666)
  chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
  fix(projects): allow owners to delete agent projects (#6533)
  Fade expanded video controls on hover (#6926)
  fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
  fix(client): resurface hidden DMs from live activity (#6885)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Aug 27, 2026
* origin/main: (21 commits)
  chore(deps): update rui314/setup-mold digest to 7e4f20a (#6663)
  chore(deps): update dependency vitest to v4.1.11 (#6667)
  chore(deps): update dependency @tanstack/react-virtual to v3.14.10 (#6666)
  chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
  fix(projects): allow owners to delete agent projects (#6533)
  Fade expanded video controls on hover (#6926)
  fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
  fix(client): resurface hidden DMs from live activity (#6885)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
sandro-sq added a commit that referenced this pull request Aug 27, 2026
…e-view

* origin/pr-6189: (170 commits)
  test(mesh): prove relay mode probes are refreshed
  fix(mesh): keep closed availability helper test-only
  fix(mesh): refresh relay admission mode safely
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  Use paired tags for standing & per-turn context (#6701)
  fix(cli): preserve signatures in event reads (#6884)
  refactor(db): finish replaceable event store extraction (#6777)
  Fix Admin feedback filter overflow (#6825)
  fix(desktop): stop pulsing addressed agents on send (#6873)
  fix(desktop): prioritize sidebar channel status (#6861)
  ...

Signed-off-by: Alessandro Joabar <sandro@squareup.com>

# Conflicts:
#	desktop/src-tauri/src/commands/mesh_llm.rs
#	desktop/src-tauri/src/mesh_llm/catalog.rs
#	desktop/src/features/sidebar/ui/AppSidebar.tsx
santhiprakash added a commit to santhiprakash/buzz that referenced this pull request Aug 28, 2026
The Add Channel dialog rendered every row with a hardcoded '#' prefix, ignoring channel.visibility and channel.channelType. The sidebar already shows a lock for private channels and a file icon for forum channels; the channel browser drifted away from that and the same channel looked different in two surfaces of the same window (block#6120).

Add a pure, unit-testable ChannelRowIcon component with getChannelRowIconKind() and use it in ChannelBrowserDialog. The sidebar now uses ChannelGlyph (introduced by block#6590), so this PR focuses on the browser surface.

Tests cover the helper directly and render the component under JSDOM to assert the right lucide-* class reaches the DOM for each channel kind.

Signed-off-by: Santhi Prakash <b.santhiprakash@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants