mirror of
https://github.com/block/buzz.git
synced 2026-10-27 20:41:57 +00:00
44d19f59a593dfb541bf40dcadbed313ca4a8f97
338 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ac5a18697c |
feat(desktop): add persistent Bestie experience (#7223)
## Summary - add a persistent, community-scoped Bestie designation for local managed agents - surface the designated agent in the sidebar, agent library, profile actions, message toolbar, and draggable floating shortcut - bloom the floating shortcut into a lightweight compact composer that reuses the normal DM timeline, reactions, presence, and send behavior - support message handoff with a bounded snapshot and a full Buzz thread link so the agent can retrieve the complete conversation ## UX details - the floating avatar and expanded panel stay above app chrome and drag as one aligned surface - closing the expanded panel returns it to its top-right anchor - each mini-composer opening starts visually fresh while messages sent during that opening remain conversational - the designated Bestie's duplicate DM entry is hidden from the regular DM list - Bestie actions are suppressed inside the mini timeline to avoid recursive handoff ## Reliability and maintainability - preserve existing retention database paths across upgrades - serialize assignment and deletion, clearing matching assignments across community scopes before an agent is removed - fence async conversation resolution against workspace and assignment changes - validate stale assignments against existing local agents before hiding DMs - share lightweight assignment state across agent cards and keep protected-feature behavior out of the shared timeline API ## Testing - `pnpm --dir desktop test` — 5,886 tests passed - `pnpm --dir desktop exec tsc --noEmit` - `pnpm --dir desktop exec biome check ...` - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings` - focused native retention and Bestie assignment/command tests — 34 passed - `VITE_BUZZ_BESTIE=1 pnpm --dir desktop build:e2e` - `VITE_BUZZ_BESTIE=1 pnpm --dir desktop exec playwright test --project=smoke tests/e2e/bestie.spec.ts` - pre-commit and differential pre-push hooks ## Rollout The UI remains gated by the `bestie` build feature. Screenshots covering setup, empty, assigned, floating, and message-handoff states are included in the PR discussion. --------- Signed-off-by: Arjun Mahanti <arjun@squareup.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
434dafe958 |
fix(desktop): discover authenticated owned relay agents (#7122)
🤖 ## Summary An agent you own could be missing from **New message → To:** and **Channel members → Add people and agents** on a machine that has never managed it. This PR lets those existing lists find your agent without requiring a shared channel first. Desktop now checks records proving you own it, rather than looking only at agents in channels you've already joined. **No new screen or control is added.** For example, an agent with verified ownership and **Who can send instructions → Only me (default)** can now appear even with no shared channels. Each screen still applies its existing access rules; this does not make every discovered agent selectable everywhere. | Screen / control | Before | After this PR alone | | --- | --- | --- | | **New message → To:** recipient picker | An owned agent absent from this machine and shared-channel bot lists could be missing. | Its named **agent** row can appear; selecting it adds a recipient chip. This is recipient selection, not a guarantee that a later message will reach or wake the agent. | | **Channel members → Add people and agents** | The same agent could be missing from **Not in this channel** search results. | Its row can appear with the existing **Add** button. If you can add members, that button submits the existing channel-membership request; finding the row alone changes no membership. | | **Stream / forum composer → @ suggestions** | An owned agent already in the channel under an ordinary member role could be missing from agent suggestions. | Its actual membership is recognized without requiring the bot role. Agents not managed on this device still need membership in that channel. | | **Pulse → Agents** | An agent absent from both local management and the server's agent list was omitted from the count and author lookup. | The count and feed's author lookup can include it; notes appear only if it has published them. | Being listed does **not** mean the agent is online, add it to a channel, or grant local Start/Edit controls. For agents not managed on this device, global **Search** still excludes those configured for “Only me”, and DM @ selection is not added here. DM @ selection and message-driven nonmember invitation are addressed in [#7124](https://github.com/block/buzz/pull/7124); the standalone forum **Invite / Cancel** flow is in [#7125](https://github.com/block/buzz/pull/7125). <details> <summary>Ownership and membership checks</summary> A discovery lead is not proof: the latest agent profile must have a valid signature and exactly one valid ownership attestation—the owner's signed link to that agent. Its response policy must be signed by that verified owner; an invalid latest policy cannot restore an older permission. Membership comes separately from the latest server-signed roster, including removals. Existing profile cards, owner labels and agent-avatar shapes also use this stricter verification: malformed or forged evidence must not supply ownership/agent classification on its own. Valid ownership was already recognized; no profile-picture or badge design changes. Attestation time conditions apply to the signed event's timestamp, not a live expiry timer. Existing legacy compatibility and builds requiring verified owner policy retain their respective rules. Discovery and sending remain separate operations, not an atomic permission check. </details> ### Review corrections - When runtime and owner policy overlap, **explicit online/away/offline from the verified latest runtime is retained**. Policy still supplies ownership/permissions; claimed runtime membership is not restored. Missing/unrecognized status stays unknown, and invalid latest policy cannot revive runtime permissions. - Discovery without runtime evidence is now **unknown**, not offline: native conversion, both IPC adapters, Pulse, Projects and profile/session consumers preserve that distinction. Unknown has no status dot and is not promoted to a deployed/running agent. - Both relay-only picker paths retain the authenticated owner, including the existing **managed by you** label. The analogous global Search projection is fixed without changing its existing “anyone” filter. - Authorized stored profile activity remains visible when liveness becomes unknown/absent or the active turn ends. History reads do not start a live subscription, grant access, or imply current availability. ### Related issue Independent base: `main`. Child: [#7124](https://github.com/block/buzz/pull/7124), then [#7125](https://github.com/block/buzz/pull/7125). Extracted from [#7114](https://github.com/block/buzz/pull/7114), retained as historical source (`98fe33ec`). [Behavior contract]( |
||
|
|
114dbf745f |
Add voice notes to desktop messages (#6978)
## Summary - add voice-note recording, preview, removal, and send controls to the desktop composer - render sent voice notes as waveform cards with scrubbing and playback-speed controls - transcode recordings locally into the existing relay video-media contract, without relay changes ## Testing - `just ci` - `pnpm exec playwright test tests/e2e/voice-note.spec.ts` - native voice-note media contract test against the relay validator ## Screenshots Focused snapshots are attached below. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
59328d5ae3 |
feat(desktop): add thread-scoped ACP session experiment (#6909)
## Why Thread-scoped ACP sessions from #6732 need an opt-in desktop rollout that preserves today’s channel policy by default. ## What - Add the default-off “Thread Scoped ACP Sessions” toggle under Settings → Experiments using the existing feature-override persistence. - Map the persisted setting to `BUZZ_ACP_SESSION_POLICY=channel|thread` for every local and provider-backed managed ACP launch. Changes apply when managed agents next start; DMs remain conversation-scoped by the backend. - Keep the desktop setting authoritative over descriptor environment and cover the UI, persistence, local spawn, provider payload, and existing Projects/Workflows experiments. ## Risk Assessment Low. The experiment defaults off and explicitly preserves `channel`; changes are limited to managed-agent launch configuration. Existing running agents are unchanged until their next start. ## Testing - `. ./bin/activate-hermit && just ci` — passed on the final restacked tree, including 5,674 desktop tests, 2,796 Tauri tests, and 1,860 mobile tests. - `cd desktop && pnpm build:e2e && pnpm exec playwright test tests/e2e/experimental-features.spec.ts --project=smoke` — 1 passed. - `. ./bin/activate-hermit && cargo test --manifest-path desktop/src-tauri/Cargo.toml session_policy --lib` — 4 passed. - `. ./bin/activate-hermit && cargo test --manifest-path desktop/src-tauri/Cargo.toml commands::agents::deploy::tests --lib` — 15 passed. - `. ./bin/activate-hermit && cargo test -p buzz-backend-kubernetes --test wire_fixtures` — 4 passed. ## Stack Info Stacked on #6732. This PR depends on #6732 and should merge after it. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> |
||
|
|
cb3144999b |
add public descriptions to agent personas (#7126)
**Category:** new-feature **User Impact:** People can add a short public description to an agent and see what it does directly on agent cards and profiles. **Problem:** Agent cards previously showed only a model label, so people had to open an agent and inspect its instructions to understand its purpose. Public metadata also needed one trustworthy lifecycle across local edits, relay catalogs, profiles, and portable snapshots. **Solution:** Add an optional owner-authored description with a 280-character visible-text policy, publish it as profile `about`, and prefer it on agent cards while retaining the model fallback. Description metadata is excluded from the spawn-content hash, remains definition-owned, and is validated independently at every untrusted or persistence boundary. <details> <summary>File changes</summary> **desktop/src-tauri/src/commands/agent_config_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/agent_discovery/relay_directory.rs** Updates relay-directory profile test publication for the expanded profile contract. **desktop/src-tauri/src/commands/agent_models_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/agent_models_update.rs** Preserves the effective `about` value when instance edits republish a complete profile event. **desktop/src-tauri/src/commands/agents.rs** Carries the effective authored description into initial managed-agent profile publication. **desktop/src-tauri/src/commands/agents_profile.rs** Adds `about` to profile reconciliation and keeps description, name, and avatar synchronized against relay state. **desktop/src-tauri/src/commands/agents_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/card.rs** Materializes the definition-owned description before minting a portable agent card snapshot. **desktop/src-tauri/src/commands/personas/create.rs** Normalizes and validates raw authored descriptions before persona persistence. **desktop/src-tauri/src/commands/personas/delete_cascade_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/inbound.rs** Validates descriptions at inbound relay ingress and applies accepted values to local definitions. **desktop/src-tauri/src/commands/personas/inbound/catalog_reconcile_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/inbound/inbound_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/mod.rs** Centralizes raw-byte validation followed by trim/empty normalization for description writes. **desktop/src-tauri/src/commands/personas/pending.rs** Revalidates descriptions before preparing public persona publications. **desktop/src-tauri/src/commands/personas/sharing.rs** Carries the optional public description through this managed-agent compatibility path. **desktop/src-tauri/src/commands/personas/snapshot.rs** Materializes definition-owned descriptions into portable instance snapshots without creating a second persisted authority. **desktop/src-tauri/src/commands/personas/snapshot/fidelity_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/snapshot/import.rs** Restores snapshot descriptions onto imported definitions while keeping linked instance copies absent. **desktop/src-tauri/src/commands/personas/snapshot/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/update.rs** Persists persona description edits, republishes linked profiles, and preserves legacy avatars during complete kind:0 replacements. **desktop/src-tauri/src/commands/personas/update/name_propagation_tests.rs** Proves description-only profile sync does not write instance state or clear a legacy avatar. **desktop/src-tauri/src/commands/team_snapshot.rs** Round-trips member descriptions through team snapshots and imported definitions. **desktop/src-tauri/src/commands/team_snapshot/tests.rs** Covers team member description export and import fidelity. **desktop/src-tauri/src/commands/teams/adopt/apply.rs** Starts adopted team catalog members without synthesizing an unauthored description. **desktop/src-tauri/src/commands/teams/adopt/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/teams/pending/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/teams/sharing/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/egress_guard_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/event_sync_team_catalog_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/agent_description.rs** Defines the canonical Rust description resolution used by profile publication and reconciliation. **desktop/src-tauri/src/managed_agents/agent_events.rs** Updates managed-agent record construction for the optional public description field. **desktop/src-tauri/src/managed_agents/agent_snapshot.rs** Includes descriptions as snapshot profile `about` metadata and validates them at decode ingress. **desktop/src-tauri/src/managed_agents/agent_snapshot_envelope.rs** Updates managed-agent record construction for the optional public description field. **desktop/src-tauri/src/managed_agents/agent_snapshot_tests.rs** Covers snapshot description export and rejection of unsafe or overlong imported metadata. **desktop/src-tauri/src/managed_agents/config_bridge/reader_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/definition_validation.rs** Adds the shared 280-character visible-text policy for public descriptions. **desktop/src-tauri/src/managed_agents/discovery/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/effective_config/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/global_config/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/mod.rs** Exports the description resolution and validation helpers to managed-agent consumers. **desktop/src-tauri/src/managed_agents/nest/render_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/parallelism.rs** Updates managed-agent fixtures for the optional description field without changing runtime configuration behavior. **desktop/src-tauri/src/managed_agents/persona_events.rs** Adds description to persona event content while deliberately excluding it from the spawn-relevant content hash. **desktop/src-tauri/src/managed_agents/persona_events/tests.rs** Pins description event round-tripping and proves description-only edits do not change the restart hash. **desktop/src-tauri/src/managed_agents/personas.rs** Initializes built-in persona records without authored descriptions for backward-compatible defaults. **desktop/src-tauri/src/managed_agents/personas/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/readiness.rs** Updates managed-agent fixtures for the optional description field without changing runtime configuration behavior. **desktop/src-tauri/src/managed_agents/restore.rs** Includes the effective description in launch-time profile reconciliation. **desktop/src-tauri/src/managed_agents/runtime/test_fixtures.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/runtime/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/spawn_snapshot/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/team_catalog/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/team_snapshot.rs** Updates managed-agent record construction for the optional public description field. **desktop/src-tauri/src/managed_agents/teams_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/types.rs** Adds optional description metadata to persona and managed-agent records and their compatibility projections. **desktop/src-tauri/src/managed_agents/types/requests.rs** Accepts optional descriptions on persona create and update IPC requests. **desktop/src-tauri/src/managed_agents/types/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/migration_avatar_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/persona_catalog.rs** Parses and validates descriptions at the untrusted community-catalog boundary. **desktop/src-tauri/src/persona_catalog_tests.rs** Covers valid catalog descriptions plus rejection of malformed, invisible, and overlong values. **desktop/src-tauri/src/relay.rs** Publishes and queries kind:0 `about` so relay profiles preserve authored descriptions. **desktop/src-tauri/src/relay/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src/features/agents/AGENTS.md** Documents description ownership, validation, snapshot, hashing, and display invariants for future changes. **desktop/src/features/agents/lib/agentDescription.test.mjs** Pins Unicode counting, paste clamping, trimming, and empty authored-description behavior. **desktop/src/features/agents/lib/agentDescription.ts** Provides shared display resolution, Unicode-scalar counting, and paste clamping for descriptions. **desktop/src/features/agents/lib/personaCatalogRelay.ts** Maps validated catalog descriptions into catalog persona projections. **desktop/src/features/agents/ui/AgentDefinitionDialog.tsx** Adds the description draft to create and edit submission while extracting identity fields from the large dialog. **desktop/src/features/agents/ui/AgentDescriptionField.tsx** Renders the public description input, helper copy, and Unicode-aware near-limit counter. **desktop/src/features/agents/ui/AgentIdentityCard.tsx** Generalizes the card second line to show a two-line description or the existing model fallback. **desktop/src/features/agents/ui/UnifiedAgentsSection.tsx** Prefers authored descriptions on persona cards and retains model labels when no description exists. **desktop/src/features/agents/ui/personaDialogState.test.mjs** Verifies edit and duplicate drafts preserve authored descriptions. **desktop/src/features/agents/ui/personaDialogState.ts** Seeds authored descriptions into edit and duplicate dialog drafts. **desktop/src/features/agents/ui/usePersonaActions.ts** Preserves descriptions when copying catalog personas into local definitions. **desktop/src/shared/api/personaTypes.ts** Defines description-bearing persona wire types in a focused module split from the size-constrained API type file. **desktop/src/shared/api/tauriPersonas.test.mjs** Verifies raw persona descriptions map into the frontend model and absent values become null. **desktop/src/shared/api/tauriPersonas.ts** Maps description fields across Tauri and preserves raw authored bytes for authoritative Rust validation. **desktop/src/shared/api/types.ts** Re-exports the extracted persona types without changing consumer import paths. **desktop/src/testing/e2eBridge.ts** Extends mock persona create, update, publication, and catalog parsing with production-shaped description behavior. **desktop/tests/e2e/agents.spec.ts** Verifies an edited description persists and appears on the agent card. </details> ### Reproduction Steps 1. Open **Agents**, edit a custom or built-in agent, and enter a sentence in **Description**. 2. Save the agent and confirm the sentence appears as the second line on its card. 3. Reopen the agent and confirm the authored description is restored; clear it and confirm the card returns to the model label. 4. Paste more than 280 Unicode characters and confirm the field keeps the first 280 characters and shows the near-limit counter. 5. Share or export/import the agent and confirm the description survives in the catalog/profile or snapshot without showing a restart-required badge for a description-only edit. ### Screenshots / Demo The focused Playwright flow `built-in persona edits persist` exercises the edited dialog, persisted value, and resulting card subtitle. Screenshots can be added after review if the field placement or two-line card treatment needs visual iteration. ### Verification - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib` — 3,029 passed - `cd desktop && pnpm test` — 5,805 passed - `cd desktop && pnpm exec tsc --noEmit` - Focused Playwright: `built-in persona edits persist` — passed - Pre-push desktop, Tauri, typecheck, test, file-size, and branch-skew gates — passed --------- Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
8dbc65d9e2 |
fix(composer): polish automatic mentions (#6956)
**Category:** improvement **User Impact:** Automatic mentions are easier to turn off and now behave consistently across conversations, settings, drafts, and repeated agent mentions. **Problem:** People found the new automatic mention behavior hard to control: turning it off in Settings did not reliably affect the composer, removing a mention could require also disabling the feature, and root/thread composers could inherit or restore surprising state. Other reported rough edges included only one of several mentioned agents becoming automatic, synthetic mentions leaking into drafts, restored mentions corrupting adjacent text, controls remaining visible in archived channels, and unclear picker feedback. See the [original feedback thread](buzz://message?channel=e62570dd-33ad-42c5-b92b-75f2689f9694&id=c949ec399274fbb0d6633da3f95712e843a67dcada214f63d72f6975b406604b). **Solution:** Polish the existing feature around the problems people encountered, keeping automatic mentions controllable and scoped to the active conversation. | Reported issue | UX fix | | --- | --- | | Turning automatic mentions off in Settings did not reliably update the composer. | The global setting and composer control stay synchronized, and disabling the feature does not clear typed text. | | Removing an automatic mention could require both deleting the mention and turning off the feature. | Removing or unchecking an agent excludes that agent for the current conversation, while explicitly re-adding the agent can restore automatic mention behavior. | | Root and thread composers could share or restore surprising selections. | Each root or thread composer keeps its own automatic audience and restores it when the user returns. A request to enable automatic mentions only in agent threads was considered; this PR keeps them available at the channel root but prevents state from leaking between the two. | | Mentioning multiple agents could leave only one saved as automatic. | Multi-agent selections remain represented in the automatic audience and restored mention chips. | | Automatic mention prefixes could be saved as if the user typed them. | Synthetic prefixes stay out of persisted drafts while authored text is preserved. | | Restored mentions could lose their separator and corrupt continued typing. | Restored multi-word mentions retain their trailing space and place the caret after it. | | Archived channels showed automatic-mention state beside a disabled composer. | Disabled composers hide automatic-mention controls while preserving the draft and restoring state when re-enabled. | | Confirmation and picker behavior made the feature feel difficult to inspect or adjust. | Confirmations dismiss with removed agents, remain open while hovered, and expose the setting before it changes; pin icons, contrast, scope copy, animation, and keyboard toggling are also clarified. | | Agent suggestions and membership state could shift during directory refreshes. | Suggestions and membership labels stay stable during refreshes, while send-time authorization still revalidates access. | ## Changes <details> <summary>File changes</summary> **desktop/src/features/agents/lib/agentAutocompleteEligibility.test.mjs** Adds coverage for the channel-roster eligibility rules used by agent mention autocomplete. **desktop/src/features/agents/lib/agentAutocompleteEligibility.ts** Aligns agent autocomplete eligibility with channel membership so available agents and their labels stay trustworthy. **desktop/src/features/channels/ui/MembersSidebar.tsx** Uses the shared member-pubkey logic when presenting and acting on channel members. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.test.mjs** Covers preference changes that must remain stable while composer controls are toggled. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.ts** Keeps the automatic-mention preference as durable user intent rather than transient composer state. **desktop/src/features/messages/lib/mentionMemberPubkeys.ts** Centralizes which member identities count as mentionable in the current channel. **desktop/src/features/messages/lib/persistentAgentAudience.test.mjs** Expands lifecycle coverage for persistent agent audiences, explicit exclusions, and restored mentions. **desktop/src/features/messages/lib/persistentAgentAudience.ts** Models automatic, explicit, and excluded agent audiences separately so user choices survive updates without leaking across composers. **desktop/src/features/messages/lib/stripImplicitAgentMentions.test.mjs** Verifies implicit automatic mentions are removed without damaging surrounding separators or authored content. **desktop/src/features/messages/lib/stripImplicitAgentMentions.ts** Strips presentation-only automatic mentions before draft persistence while preserving whitespace and authored text. **desktop/src/features/messages/lib/useMentions.ts** Routes mention insertion and removal through the composer-local audience lifecycle. **desktop/src/features/messages/lib/useRichTextEditor.ts** Preserves mention-chip structure and caret placement when automatic mentions are restored. **desktop/src/features/messages/ui/ComposerAddressControls.test.mjs** Updates control-state expectations for disabled automatic mentions and restored pin affordances. **desktop/src/features/messages/ui/ComposerAddressControls.tsx** Makes automatic-mention state, disabled presentation, and pin controls visually explicit. **desktop/src/features/messages/ui/MentionAutocomplete.test.mjs** Adds coverage for roster labels, pin state, and picker behavior after mention selection. **desktop/src/features/messages/ui/MentionAutocomplete.tsx** Keeps the shortcut picker open for repeated selection and restores visible automatic-mention pin indicators. **desktop/src/features/messages/ui/MessageComposer.tsx** Scopes automatic mention state to each root or thread composer and coordinates restoration, draft persistence, and sending. **desktop/src/features/messages/ui/MessageComposerToolbar.tsx** Passes the effective automatic-mention state into the toolbar presentation. **desktop/src/features/messages/ui/composerAgentKeyboard.test.mjs** Updates keyboard interaction coverage for toggling agents in place. **desktop/src/features/messages/ui/useAddressedAgentMentionRestore.ts** Restores automatic mention chips after lifecycle changes without moving or duplicating authored content. **desktop/src/features/messages/ui/useAgentAddressLockPicker.test.mjs** Substantially expands coverage for toggles, exclusions, synchronization, and picker dismissal rules. **desktop/src/features/messages/ui/useAgentAddressLockPicker.ts** Keeps the picker usable across repeated choices and preserves explicit per-agent intent while settings change. **desktop/src/features/messages/ui/useAlwaysAddressShortcut.ts** Makes the keyboard shortcut toggle the highlighted automatic audience choice without replacing unrelated selections. **desktop/src/features/messages/ui/useAutoPinMentionedAgents.ts** Owns composer-local automatic-mention lifecycle behavior, including restoration, exclusions, deletion, and disabled-state handling. **desktop/src/features/messages/ui/useComposerMentionPicker.test.mjs** Adds focused picker lifecycle coverage for selection, hover, and dismissal behavior. **desktop/src/features/messages/ui/useComposerMentionPicker.ts** Prevents premature picker dismissal while the user is interacting with its controls. **desktop/src/features/messages/ui/useDraftPersistSnapshot.ts** Persists only user-authored draft content rather than implicit automatic mention decorations. **desktop/src/shared/lib/keyboard-shortcuts.ts** Updates the automatic-mention shortcut description to match its toggle behavior. **desktop/src/testing/e2eBridge.ts** Extends the desktop test bridge with the state needed to exercise roster and automatic-mention transitions. **desktop/tests/e2e/mentions.spec.ts** Covers roster-based labels, managed-agent invitation, revocation, and recovery behavior in the complete mention flow. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Adds end-to-end coverage for root/thread isolation, preference synchronization, manual exclusions, draft hygiene, restored chips, separators, hover behavior, and disabled presentation. </details> ## Reproduction Steps 1. Open a channel with at least two available agents and enable automatic mentions from the composer mention control. 2. Select multiple agents, remove or uncheck one, and confirm subsequent composer updates keep that agent excluded while the others remain automatic. 3. Open a thread, choose a different automatic audience there, and switch between the thread and root composer; confirm each composer retains only its own choices. 4. Disable automatic mentions and confirm the draft text remains unchanged while automatic chips and controls show the disabled state; re-enable the setting and confirm eligible automatic chips return. 5. Delete an automatic mention chip, then explicitly add the agent again; confirm it immediately returns as an automatic mention without disturbing spaces or the caret, including for a multi-word name. 6. Reload with a saved draft and confirm implicit automatic mentions were not persisted as authored draft text. 7. Use the automatic-mention keyboard shortcut and picker repeatedly; confirm the picker remains open for additional choices and the highlighted agent toggles in place. ## Validation Validated at `34d208b47d64a9816f88e10a46bcfd479e917d75` after rebasing onto `origin/main` (`69096c9a8`): - Desktop unit tests: 5,731 passed, 0 failed. - Desktop TypeScript typecheck: passed. - Desktop E2E build: passed; emitted only existing chunk and dynamic-import warnings. - `pnpm check`: exited successfully; 4 warnings and 5 informational findings are in unrelated files introduced by current main. ## Screenshots/Demos The behavioral changes are covered by the focused desktop E2E scenarios above. Screenshots can be attached from the screenshot-producing automatic-mention E2E after the PR is created. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
2f66ee24a0 |
feat(desktop): add team sharing to community catalog (#3995)
Stack: [#5112](https://github.com/block/buzz/pull/5112) → this PR Stacks on the team catalog backend PR (#5112). Contains the `desktop/**` changes that turn the add-agent surface into a single Community Catalog browsing both shared agents and shared teams. ## Owner catalog sync pipeline `usePersonaSync.ts` gains a hydration pipeline in `startPersonaSync` so a fresh device that comes online after another already published the owner's 30178 catalog head reconstructs the complete state without falsely retracting it. The owner's persona/team/managed-agent/30178/deletion history is backfilled up front (a live-only subscription gets no history — reconnect-replay's since-cursor is undefined until the first live event), then a live subscription takes over. Four properties keep a fresh sync from purging the owner's valid shared head: - **Paged backfill with a safe termination guarantee.** The relay serves each REQ newest-first and clamps `limit` to its advertised `max_limit`, so a large owner's history overflows one page — a newer 30178/30176 could return in-page while an older required 30175 constituent falls beyond it. `fetchOwnerHistoryToExhaustion` pages the full window with the `until` time cursor (the only cursor the WS REQ filter exposes; the DB `before_id` keyset is REST-only), deduping the inclusive-boundary rows. A short page terminates normally. A full page whose oldest event cannot advance the time-only cursor is a **dense boundary** — more than one page of events share one `created_at` second, which the WS filter has no `(created_at, id)` cursor to escape — and raises `PersonaHistoryDenseBoundaryError` rather than silently completing as if the history were exhausted and dropping the older constituents behind it. - **Constituents-before-catalog ordering.** Over the complete batch, `orderCatalogHeadsLast` stably defers every 30178 head past its 30175/30176 constituents. Reconciling a 30178 head before its personas hydrate makes the inbound team refresh fail member resolution and queue a dominating false tombstone; deferring the heads guarantees the constituents are all applied first, while newest-wins order within every other coordinate is untouched. - **Hydration boundary for concurrent live events.** The backfill fetch and the live subscription start concurrently into one reconcile chain. A live or replayed 30178 that arrives before the backfill reconciles its constituents would reproduce the same false-tombstone purge, so live events are buffered until the ordered backfill is dispatched and then drained in arrival order. Steady-state live events (after hydration completes) reconcile immediately. - **Explicit backfill failure policy.** A transient history-fetch rejection is retried with bounded backoff. When backfill cannot complete — retries exhausted, or a deterministic dense boundary — the pipeline enters a **degraded-live** state rather than leaving the subscription permanently inert with live events accumulating in the buffer: the hydration boundary still opens so buffered and future live events keep reconciling, but the whole catalog dependency set is dropped — the 30178 head, its 30175/30176 constituents, and any kind-5 deletion carrying a dependency-targeting `a` tag (classified by scanning *all* `a` tags, matching the backend's deletion router, which `find_map`s across every tag and routes the first signer-owned coordinate — so a malformed or foreign first `a` tag ahead of an owned 30176 cannot slip a destructive deletion through) — because backfill never fully hydrated the owner's constituents. Dropping only the 30178 head is not enough: the backend refreshes the catalog head after every team/persona save, and live delivery is newest-first, so a 30176 edit that adds a *new* member would reach the backend before that member's 30175 and falsely tombstone a witness-holding device's valid team. Holding the prior hydrated run's constituents on disk only proves the *old* revision is resolvable — it says nothing about a new member — which is why the entire dependency set is held rather than just the head. 30177 managed-agent runtime policy stays live (it drives no catalog refresh). A degraded device stays stale on team/persona edits until it self-heals on the next effect re-run (restart, or an identity/community switch) — the correct trade against destroying valid shared state. ## Data layer Relay paging, signature verification, NIP-33 head selection, and untrusted-content parsing for the kind `30178` team catalog live natively in the `fetch_team_catalog` Tauri command (`team_catalog.rs`), structurally mirroring `fetch_persona_catalog` (`persona_catalog.rs`). A catalog refresh crosses IPC once and never verifies a signature on the webview thread. `teamCatalogRelay.ts` is now a thin presentation and local-linkage layer over the verified projection — it shapes entries for display and links each to a local team, and never parses or verifies. Parsing is all-or-nothing, identical to the add-time re-fetch in `add_team_from_catalog`: a team with any invalid member fails to parse and the publication is dropped from the catalog, matching persona behavior. **Behavior delta:** the previously reviewed partial-render of invalid-member teams — a warning banner on an entry that could never be added — is removed. Invalid publications are dropped entirely rather than surfaced as un-addable. ## Hooks `useTeamCatalogRelay.ts` mirrors the persona catalog hook: a community-keyed query over `fetch_team_catalog`, live invalidation on kind `30178`, share/unshare, and add-from-catalog (which invalidates both the teams and personas stores, since adopting a team copies its members as local personas). ## CommunityCatalogDialog Single unified surface replacing the former separate dialogs. Agents and Teams appear as labeled sections with type-tagged selection and a teams-preferred launch. `TeamsSection`'s discover entry and the new-agent card both open this one dialog. `PersonaCatalogDialog.tsx` is removed; persona browsing now lives inside the unified dialog. ## TeamShareDialog Publishes and unshares team catalog entries via `set_team_shared`. ## e2e + screenshots `team-catalog.spec.ts` covers the browse + adopt flow; `team-catalog-screenshots.spec.ts` produces the pixel-regression set. `e2eBridge.ts` gains `mockTeamCatalogPublications`, which mirrors the native command's head selection and shared gate and performs only a shallow `v`/`name`/`members`-array shape check; per-member validation stays in the Rust command. ## Follow-ups None. The shared agent-definition text-safety policy (Unicode-control/bidi/zero-width rejection) already covers the team surface: #5112's `validate_team_catalog_content` chokepoint gates every field delivered verbatim to the harness on both the publish and adopt paths, and this PR's parse layer consumes that verified projection rather than re-validating on the webview thread. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
a89f5df4c5 |
fix(desktop): complete project empty and context states (#6980)
## Summary First-time project users can now create a project directly from the empty state instead of reaching a dead end. The right-side project context also uses the intended Activity label and aligns its first row with the channel header. The populated and empty project views share one creation flow, so navigation, success feedback, and compatibility warnings remain consistent. ### Related issue Related: #6939 ### Testing - Desktop check, TypeScript typecheck, and repository file-size gate - 10 focused project overview context unit tests - E2E-mode build and 3 focused Playwright scenarios covering empty-state creation, right-sidebar alignment, and Activity context Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
9733863516 |
perf(desktop): restore project context during startup (#6939)
## Summary Project channels previously appeared as ordinary channels until the community-wide project enumeration completed, delaying the contextual right rail most noticeably on larger relays. - Restore the last fully validated, relay-and-identity-scoped project collection immediately while keeping live relay data authoritative. - Resolve the active channel's project home through scoped `#buzz-channel` queries instead of waiting for the complete project scan. - Keep snapshots aligned with community removal and relay reconnect invalidation. ### Related issue None found. ### Testing - Pre-push `file-size-check`, `desktop-check`, `desktop-typecheck`, and `desktop-test` - Project enumeration, snapshot persistence, and relay invalidation unit coverage - Targeted Playwright scenario: project sidebar rows open the home channel and nest extra channels No numeric startup benchmark was captured; this draft validates the cache and scoped-query behavior while leaving timing measurement for review. --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
ceb8ba605f |
fix(desktop): accent-colored mention badges that count thread mentions (#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> |
||
|
|
eb8e97cfe7 |
fix(desktop): show edited head content in thread panel (#6887)
## Why Fixes #6886. In the channel timeline an edited message shows its newest body, but opening its thread panel showed the stale pre-edit body until the async thread-aux backfill landed (and permanently if it raced or missed). ## Root cause `desktop/src/features/messages/lib/independentThreadPanel.ts` built `events = [head, ...replyEvents]`, discarding the head's aux overlay events (edits `kind:40003`, deletions, reactions) already present in the channel window. `formatTimelineMessages` only applies an edit when the edit event sits in the same array as its target, so the thread head relied solely on `withThreadAux` to re-supply it. ## What - Carry the head's non-content aux events (via `!isTimelineContentEvent`) from the channel window into the thread head's event array, deduped against `replyEvents` by id. - Thread head is now byte-identical to the main timeline the instant the thread opens, independent of the async aux fetch. - Restricted to non-content kinds so reply content rows never leak in. ## Tests - Unit regression `independentThreadPanel.test.mjs` (6 cases): red pre-fix (`"two PRs"`), green post-fix (`"these PRs"`). - e2e `thread-head-stale-edit.spec.ts`: delays thread-replies aux to force the channel-window-vs-thread-aux divergence, then opens the thread via the reply button and asserts the panel head shows the edited body. Verified red pre-fix (stale `message-thread-panel` head), green post-fix. - Full desktop unit suite: **5562 pass / 0 fail**. `biome` + `tsc --noEmit` clean. Origin: Buzz channel `fix-reply-thread-old-view-bug` (#464b982f-fc05-42c8-918f-409e1f05a1df). --------- Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> |
||
|
|
de188ebf06 |
feat(projects): add agent and CLI project-home support (#6590)
## Summary - inject bounded project-home identity and repository context into managed agent sessions - add project-aware CLI flows for creating projects, repositories, issues, and related channels - match project homes through the existing relay query surface, then filter channel metadata client-side This is Part 1 of the channel-first Projects stack. Part 2 contains project creation and model foundations. ## Testing - `cargo fmt --all -- --check` - `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings` - `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test ignored - full pre-push gate passed ## Post-Deploy Monitoring & Validation - validate project-home agent context and project-aware CLI commands against a staging relay - healthy signals: project context matches the active channel, explicit repo coordinates remain stable, and normal channels receive no project block - failure signals: cross-channel project context, duplicate project creation, or commands targeting an unrelated repository; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> |
||
|
|
29f2054c69 |
highlight search terms in results and messages (#6702)
**Category:** fix **User Impact:** Search terms are now highlighted in yellow in Cmd+F results and in the message opened from a result. **Problem:** Search returned relevant messages, but users still had to reread each preview and destination message to discover where the query appeared. Search navigation could also lose or misapply highlighting during rapid typing, repeated navigation, thread opening, and forum navigation. **Solution:** Render match-focused previews with a shared yellow treatment, carry the result query through navigation, and apply it only to the clicked destination. The implementation binds highlights to the debounced result set, supports token and prefix matching, and handles channels, threads, forums, diffs, and wave messages consistently. <details> <summary>File changes</summary> **desktop/src/app/AppShell.tsx** Carries the active result query into search-hit navigation so the destination can preserve the user's context. **desktop/src/app/navigation/searchHitEventCache.ts** Adds a bounded, one-shot cache for result queries and navigation IDs alongside cached search-hit events. **desktop/src/app/navigation/searchHitNavigation.test.mjs** Covers query retention, one-shot consumption, repeated same-route activations, forum posts, forced routing, and cancellation. **desktop/src/app/navigation/searchHitNavigation.ts** Associates each search-result activation with a unique navigation ID and forwards it to channel or forum destinations. **desktop/src/app/navigation/useAppNavigation.ts** Extends channel and forum navigation to carry search navigation state without changing normal navigation behavior. **desktop/src/app/routes/ChannelRouteScreen.tsx** Consumes search highlight state after navigation and retains it for the selected message while clearing it on community context changes. **desktop/src/app/routes/channels.$channelId.posts.$postId.tsx** Validates and forwards forum search-navigation IDs. **desktop/src/app/routes/channels.$channelId.tsx** Validates and forwards channel search-navigation IDs. **desktop/src/features/channels/ui/ChannelPane.tsx** Passes the selected result ID and query into the main timeline and open thread panel. **desktop/src/features/channels/ui/ChannelPane.types.ts** Defines the destination-highlight contract for channel panes. **desktop/src/features/channels/ui/ChannelScreen.tsx** Routes destination highlighting to either forum content or the channel timeline. **desktop/src/features/channels/ui/ChannelScreen.types.ts** Defines route-level search-highlight inputs. **desktop/src/features/channels/ui/ForumChannelContent.tsx** Forwards search context into expanded forum threads. **desktop/src/features/forum/ui/ForumThreadPanel.tsx** Highlights the matching text in the selected forum post or reply. **desktop/src/features/forum/ui/ForumView.tsx** Carries selected-result context from forum routing into the thread panel. **desktop/src/features/messages/ui/DiffMessage.tsx** Forwards the query into diff descriptions and rendered diff content. **desktop/src/features/messages/ui/DiffViewer.tsx** Provides a highlighted match excerpt for structured diffs and highlights fallback raw diff text. **desktop/src/features/messages/ui/MessageRow.tsx** Passes destination queries into normal Markdown, diff, and wave message renderers. **desktop/src/features/messages/ui/MessageThreadPanel.tsx** Applies highlighting to the selected thread head or reply without tinting unrelated messages. **desktop/src/features/messages/ui/WaveMessageAttachment.tsx** Highlights matches in wave-message fallback text. **desktop/src/features/search/lib/searchMatch.test.mjs** Covers case-insensitive, literal, multi-term, prefix, one-character, and late-preview matches. **desktop/src/features/search/lib/searchMatch.ts** Centralizes token extraction, case-insensitive match splitting, and match-focused preview generation. **desktop/src/features/search/ui/HighlightedSearchText.tsx** Adds the reusable accessible mark renderer used by result and specialized message surfaces. **desktop/src/features/search/ui/TopbarSearch.tsx** Highlights result previews, centers excerpts around matches, strips search operators, and hides stale results during debounce transitions. **desktop/src/features/sidebar/ui/AppSidebar.types.ts** Updates the sidebar search callback contract to include the result query. **desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx** Passes query-aware result selection through the pinned search entry point. **desktop/src/shared/lib/rehypeSearchHighlight.ts** Uses the shared matcher to mark every matching Markdown text segment while leaving code blocks untouched. **desktop/src/shared/lib/searchHighlightStyle.ts** Defines one yellow highlight treatment for light and dark themes. **desktop/src/shared/ui/markdown/nodeCache.test.mjs** Verifies all Markdown matches, one-character scoped searches, code exclusion, and transient cache behavior. **desktop/src/shared/ui/markdown/nodeCache.ts** Enables transient highlighting for one-character scoped searches without polluting the Markdown cache. **desktop/tests/e2e/smoke.spec.ts** Exercises result and destination highlighting, same-route forum activation, and stale-query suppression end to end. </details> ## Reproduction steps 1. Open a channel containing a longer message and press Cmd+F. 2. Search for a word that appears later in the message, such as `mentions`. 3. Confirm each matching result shows the word in yellow and keeps the match visible in its preview. 4. Open a result and confirm the same term is highlighted in yellow in the destination message. 5. Repeat with a thread reply or forum post, and quickly change the query to confirm stale results are not selectable. ## Demo - **Before:** Search results returned the correct message but offered no visual indication of where the query matched; opening the message also showed no match treatment. - **After:** Every matching term is marked in yellow in the result preview and in the exact channel, thread, or forum message opened from that result. --------- Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
e760c51820 |
feat(workflows): discover trigger filter values (#6712)
**Category:** improvement **User Impact:** Workflow authors can discover people and messages while configuring trigger filters, then see readable enriched labels instead of raw identifiers. **Problem:** Author and message filters required users to know and paste raw public keys or event IDs, and configured workflows surfaced those opaque values afterward. **Solution:** Add network-backed pickers and presentation enrichment while keeping deterministic local public-key and event-ID fallbacks authoritative whenever discovery is unavailable or untrusted. Related issue: none found. <details> <summary>File changes</summary> **desktop/src/features/workflows/ui/WorkflowAuthorPicker.tsx** Adds channel-aware author discovery, profile search, keyboard navigation, loading states, and deterministic public-key fallback selection. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Uses enriched trigger presentation when building the workflow card’s readable summary. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Keeps Escape scoped to an active filter picker before allowing the inspector or dialog to close. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Threads channel context into trigger filters and renders enriched author/message summaries in the workflow sequence. **desktop/src/features/workflows/ui/WorkflowMessagePicker.tsx** Adds paged channel-history discovery, message search, exact event lookup, profile labels, keyboard navigation, and bounded results. **desktop/src/features/workflows/ui/WorkflowRichTriggerDescription.tsx** Renders compact author identity details and loading presentation inside trigger summaries. **desktop/src/features/workflows/ui/WorkflowTriggerConditions.tsx** Connects author and message filter accordions to their pickers while preserving selected and excluded condition semantics. **desktop/src/features/workflows/ui/useWorkflowAuthorPresentation.ts** Resolves configured author keys to trusted display labels with deterministic fallbacks. **desktop/src/features/workflows/ui/useWorkflowTriggerPresentation.ts** Enriches configured message IDs only after validating the fetched event and channel. **desktop/src/features/workflows/ui/workflowAuthorCandidates.test.mjs** Covers author candidate normalization, ordering, deduplication, and fallback behavior. **desktop/src/features/workflows/ui/workflowAuthorCandidates.ts** Builds stable author candidates from channel members, profiles, and raw public keys. **desktop/src/features/workflows/ui/workflowConditionExpression.ts** Allows message IDs to participate in basic trigger-filter parsing. **desktop/src/features/workflows/ui/workflowDefinition.ts** Accepts enriched trigger text when generating workflow card labels. **desktop/src/features/workflows/ui/workflowMessageCandidates.test.mjs** Covers event validation, source merging, deterministic ordering, and exact-lookup enrichment boundaries. **desktop/src/features/workflows/ui/workflowMessageCandidates.ts** Validates message candidates by event kind, channel, and exact event ID before permitting enrichment. **desktop/src/features/workflows/ui/workflowTriggerDescription.test.mjs** Covers readable selected/excluded author and message descriptions plus loading fallbacks. **desktop/src/features/workflows/ui/workflowTriggerDescription.ts** Builds concise enriched trigger descriptions while retaining stable raw-ID fallbacks. **desktop/tests/e2e/workflow-local-controls.spec.ts** Exercises picker discovery, selection toggles, Escape ownership, bounded scrolling, and enriched workflow summaries. </details> ## Reproduction steps 1. Open Workflows and create a workflow for a channel with members and message history. 2. Choose **Reaction Added** as the trigger and expand **Author**. 3. Confirm channel members and fetched profile results are discoverable, searchable, and keyboard accessible; choose one. 4. Expand **Message**, confirm recent channel messages appear in a bounded list, and choose one. 5. Toggle either selected filter between **is** and **is not**, then collapse the inspector and confirm the sequence summary stays readable. 6. Add a send-message step and create the workflow; confirm its card uses the resolved author and message labels. 7. Repeat while discovery is unavailable and confirm raw public keys/event IDs remain selectable and authoritative. ## Screenshots ### Author discovery  ### Message discovery  ### Selected filter summaries  ### Enriched workflow card  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <0366ccd5ee09c2779a9d6bd6683daa17c16a508a51f6a7e7314018dab8fdc49b@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> |
||
|
|
db5617dd15 |
fix(desktop): emit singular mention feed category so alerts route correctly (#6665)
## Problem Mentions — and thread replies that @-mention you — played the **Needs action** sound instead of the **@Mentions** sound. Reported by @morgmart: "I set Needs action to a different sound and it's the only one I ever hear." ## Root cause Two vocabularies got conflated in #475: - **Section / filter vocabulary** (plural): `mentions`, `needs_action`, `activity`, `agent_activity` — the shape of `FeedSections`, the `--types` filter, and the agent-facing CLI docs. - **Per-item category vocabulary** (singular for mention): `mention`, `needs_action`, `activity`, `agent_activity` — the `FeedItemCategory` contract in `desktop/src/shared/api/types.ts`, unchanged since #12. The Tauri feed builder reused the filter string `"mentions"` as each mention item's `category`. Only one word differs between the vocabularies, so only mentions broke. Every frontend consumer compares against the singular, so real mentions never matched and fell through to the resolver's `needs_action` fallback. The E2E mock bridge emits the singular form, so tests never saw the drift. ### Symptoms this fixes (all from the one mislabel) - Mentions and mentioning thread replies played the Needs-action sound - Mention notifications used the Needs-action title format - Mentions in muted channels were suppressed (the mute-bypass never fired) - Inbox / Home feed labelled mentions "Channel update" - Channel activity popover's mentions list was always empty ## Fix **Fix the owner, not the symptoms.** `FeedItemInfo.category` becomes a `FeedItemCategory` enum whose serde form is exactly the TS union, so a misspelled category can't compile at the producer. A serialization test pins each variant to its wire string. **Frontend:** `slotForFeedKind` maps every known category explicitly. The `needs_action` fallback for unknown categories is **kept on purpose** — a contract drift should cost the user the wrong sound, not a missed alert — but it now `console.warn`s so the drift is visible to developers instead of masquerading as intended behavior. `e2eBridge.ts` and `tauri.ts` now derive the category type from `types.ts` instead of retyping it. Not touched: the plural `--types` filter and `FeedSections` keys. Those are the section vocabulary and are correct as-is. ## Verification - `just ci` green (file-size ratchet, Rust/Tauri/desktop/mobile tests, desktop + web builds) - New tests: 2 Rust (`feed_item_category_serializes_to_frontend_contract`, `feed_item_from_event_carries_singular_mention_category`), 3 TS in `sound.test.mjs` incl. one that feeds the old `"mentions"` string and asserts fallback + warning - **Runtime, dev build against the production relay:** controlled test from an agent identity into a test channel — - mention in channel → @Mentions sound, inbox shows "Mentioned in" ✅ (was Needs-action) - thread reply with mention → @Mentions sound, once ✅ (was Needs-action) - plain thread reply in the channel being viewed → silent, as designed ✅ ## Reviewers - @tlongwell-block — #475 introduced the plural category; please confirm it wasn't intentional - @wesbillman — owner of the original `FeedItemCategory` contract (#12) and most of the feed builder - @taylorkmho — owner of the sound-slot model and resolver (#968); the fallback-with-warning shape is the part to weigh in on - cc @klopez4212 --------- Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2d280376ad |
perf(desktop): persist channel heads, collapse thread reads and reply sends (#6572)
## Summary
Lands the build-now items from the desktop latency plan
(#ui-performance-deep-dive) as one change. Every perceived-latency hot
path a user hits on launch, channel open, thread open, and reply send
drops one or more round trips.
**A1 — persisted channel heads (the big one).** Native WAL SQLite cache
(`desktop/src-tauri/src/channel_head_cache.rs`) keyed by `{pubkey,
relayUrl}` scope, 32 rows/scope LRU, 1 MiB per-row drop cap,
schema-version reset, corrupt-row tolerance, checkpointed on shutdown.
Three blocking-pool commands: `channel_head_cache_load` / `_store` /
`_clear`. On the renderer side, `CommunityQueryProvider` kicks off
hydration of up to 12 heads when it constructs the query client — the
app, splash and relay preconnect mount immediately; only
`useChannelMessagesQuery` awaits the seed (`channelHeadHydration`), then
consumes a one-shot hydrated gate so a hydrated channel pays **zero**
`get_channel_window` calls on mount and exactly **one** on the
post-subscription refresh, whose response replaces page zero wholesale.
That refresh fires whether live-subscription setup succeeds or fails,
and is sequenced behind hydration so it is always a distinct
authoritative fetch (see Review follow-ups). Bounds-only persisted heads
(zero rows) are not hydrated and take the cold loading path. The
timeline loading latch recognizes native-hydrated rows as restart-safe
so they paint immediately instead of holding a skeleton. The cache is a
paint accelerator only — the relay response is always authoritative.
Replaces the legacy localStorage `messageSnapshot.ts` (removed, -401
lines).
Kill switch: `VITE_BUZZ_CHANNEL_HEAD_CACHE=off` at build time or
`localStorage["buzz-channel-head-cache"] = "off"` at runtime. Cache is
cleared on community removal and scoped per identity, so a replaced
signer never sees the previous identity's rows.
**B1 — thread aux in one response.** Relay thread filters accept
`include_aux`; the bridge appends the same authorized two-hop
reactions/edits/deletions closure a channel window gets
(`build_aux_query` shared with the window path). Renderer
`useThreadReplies` drops its two follow-up aux fetches. `next_cursor` is
computed from reply-kind rows only since aux rows are unpaged.
Documented in `docs/bridge-channel-window.md`. Thread queries keep
`staleTime: 0` (`bcfe04e2f`): an earlier revision raised it to 30s,
which CI's `thread-unread.spec.ts` caught — once the user leaves a
channel, the live subscription stops feeding that thread's cache, so a
reopen must always take the (now single) authoritative read.
**B2 — cached root on reply send.** `send_channel_message` gains
`root_event_id`; when the renderer already holds the parent (channel or
thread cache) it passes the NIP-10 root, and native signs without the
relay round trip that `resolve_thread_ref` used to make. Strict hex
parse; `root_event_id` requires `parent_event_id`; absent root falls
back to the existing relay resolution. The renderer never sends a
guessed root.
**B4** general HTTP pool idle 10s→300s, max idle per host 1→2. **B5**
relay preconnect fires as soon as identity is ready instead of waiting
for `requestIdleCallback`. One e2e test (`relay-reconnect.spec.ts`
"service restart close resets accumulated backoff") had been relying on
the idle-callback batching to skip past its own seeded dial failures
before the channel list painted; `8133d70bb` makes it wait for the
connected state instead (test-only, still fails with the 1012 backoff
reset disabled). **B6** profile freshness 60s→10 min (both the in-memory
entry check and the query `staleTime`). Tradeoff: another user's
display-name/avatar edit can take up to 10 min to propagate to a client
that already holds their profile (relay reconnect refetches
`users-batch` but resolves from the still-fresh per-pubkey entry); your
own edits still evict the entry immediately (`evictUsersBatchEntries` in
`useUpdateProfileMutation`).
### Related issue
Follows #6456/#6457/#6459/#6460 (already merged). #6455 is the
measurement instrument and is intentionally not folded in. No duplicate
PR found.
### Review follow-ups
Addressing Carl's reviews
[5001114109](https://github.com/block/buzz/pull/6572#pullrequestreview-5001114109)
and
[5002596542](https://github.com/block/buzz/pull/6572#pullrequestreview-5002596542),
each pushed as new commits (no rebase):
- `4f06b7770` fix(desktop): mount app while channel heads hydrate;
always revalidate — provider no longer gates children on the cache load;
`refreshAfterSubscribe` runs on subscribe failure too; bounds-only heads
skipped at seed; seed merges into an existing window store. +3 tests.
- `35834cb31` fix(relay): drain aux closure hops across the page clamp —
`query_all_pages` walks the `(created_at, id)` keyset via
`until`/`before_id` until a short page (`AUX_PAGE_LIMIT` =
`DEFAULT_MAX_PAGE_LIMIT`, `AUX_MAX_PAGES` = 64 warn+truncate) so
one-shot `limit: 1000` newest-first no longer drops the oldest
edits/deletions. +3 tests; `docs/bridge-channel-window.md` updated.
- `db21b0531` merge of `origin/main` `e23632941` (#6558, #6312 — no
overlap).
- `5a5566c0f` fix(desktop): sequence post-subscribe refresh behind
channel head hydration — `refreshChannelWindowMessages` awaits
`channelHeadHydration()` and, for a hydration-seeded query (`data !==
undefined && dataUpdatedAt === 0`), the in-flight snapshot fetch before
invalidating. Without this, a subscription that settles before the
SQLite load invalidated a data-less in-flight query; TanStack dedupes
that onto the existing fetch (`query-core` `fetch()` only cancels when
`state.data` exists), which returned the seeded snapshot — 0
authoritative fetches. Regression test reproduces Carl's exact ordering
(fails at `35834cb31` with 0 calls), plus a cold-channel guard that the
fix does not double-fetch.
- `b129231c8` fix(desktop): let concurrent post-hydration refreshes
share one window fetch — found independently by Max and Wren reviewing
`5a5566c0f`: subscribe settlement + reconnect both wake on the same
snapshot promise and both invalidate; the second (default
`cancelRefetch: true`) cancelled and replaced the first authoritative
fetch (3 queryFn calls, not 2, and the cancelled Tauri invoke still hits
the relay). The seeded branch now invalidates with `cancelRefetch:
false` so a second waker joins the in-flight fetch; cold/warm keep the
default (`test_canceled_stale_fetch_cannot_overwrite_catch_up_window`
relies on it). Concurrent regression test fails at `5a5566c0f` with 3.
### Testing
At `b129231c8` (PR head; verified in one shell with `git rev-parse HEAD`
= `b129231c8`): `pnpm check`, `tsc --noEmit`, desktop unit 5,393 / 0,
Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` +
`relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh
`build:e2e`, pre-push hooks green.
At `5a5566c0f`: `pnpm check`, `tsc --noEmit`, desktop unit 5,392 / 0,
Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` +
`relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh
`build:e2e`, pre-push hooks green.
At `35834cb31`: desktop unit 5,390 / 0; `cargo test -p buzz-relay --lib`
910 / 0; fmt + clippy `-D warnings` clean; Playwright 32 / 32 (same
specs minus affordance); GitHub CI green on every job except Smoke (3)
(unrelated project-review row-count + messaging timing flake, per Carl)
and Unit Tests (sherpa cache skeleton, below).
Earlier, all at `8133d70bb` (this PR head is `0c492366d` =
|
||
|
|
de8a2741c7 |
feat(desktop-messages): show compact Buzz link metadata (#6252)
**Category:** improvement **User Impact:** Buzz links in messages now resolve into compact, descriptive chips with predictable navigation, while tooltips use the neutral secondary surface consistently across light and dark themes. **Problem:** Raw Buzz-native links and opaque identifiers make message context difficult to scan, while oversized previews disrupt the conversation. Missing, delayed, or deleted metadata also needs a stable presentation that does not mislead users or break navigation. **Solution:** Keep message and issue chips metadata-independent: message chips render only the channel name, while issue chips render only the repository name—never fetched content/title or an event hash. Fetched context remains available in each hover tooltip. Other native links continue to resolve into concise inline labels, with muted secondary fallbacks, distinct deleted states, and existing navigation paths preserved. This also makes two explicit shared-tooltip behavior changes: 1. **Color:** shared and rich tooltips move from the primary color pair to the neutral secondary semantic pair, including dedicated huddle-token handling and matching rich-descendant foregrounds. 2. **Hover behavior:** tooltip roots set Radix `disableHoverableContent`, and tooltip content is pointer-transparent, so moving off the trigger dismisses the tooltip instead of letting the pointer enter and keep the popover open. Together these preserve readable contrast, authoritative project context, and predictable hover dismissal. <details> <summary>File changes</summary> **desktop/src/features/communities/useCommunityInit.ts** Primes channel metadata needed to resolve native links promptly. **desktop/src/features/messages/lib/messageLinkMetadata.test.mjs** Covers message preview extraction, fallbacks, and deleted-message semantics. **desktop/src/features/messages/lib/messageLinkMetadata.ts** Normalizes fetched message metadata into compact labels and status-aware presentation. **desktop/src/features/messages/ui/SentFromThreadLine.tsx** Provides thread context to linked-message metadata resolution. **desktop/src/shared/lib/useResolvedLinkPreviews.ts** Exposes resolved preview state for native entity chips. **desktop/src/shared/styles/globals/markdown.css** Adds compact chip layout and semantic disabled-state styling. **desktop/src/shared/styles/globals/theme.css** Defines the semantic colors used by unavailable and deleted chips. **desktop/src/shared/ui/markdown.test.mjs** Exercises native-link rendering and provider-backed entity metadata behavior. **desktop/src/shared/ui/markdown.tsx** Routes native markdown links through metadata-aware chip components without changing navigation contracts. **desktop/src/shared/ui/markdown/ChannelDeepLink.test.mjs** Covers resolved, unresolved, private, archived, and forum channel presentations. **desktop/src/shared/ui/markdown/ChannelDeepLink.tsx** Renders channel names and concise channel metadata inline with stable fallbacks. **desktop/src/shared/ui/markdown/MessageLinkPill.tsx** Renders loading, ready, unavailable, DM, forum, and deleted message states compactly. **desktop/src/shared/ui/markdown/entityLinks.tsx** Enriches repository, issue, and pull-request chips and uses containing-project context in tooltips. **desktop/src/shared/ui/markdown/types.ts** Extends markdown runtime metadata required by native chip rendering. **desktop/src/shared/ui/markdown/useMessageLinkMetadata.ts** Fetches and classifies linked-message metadata while preserving navigable fallbacks. **desktop/src/testing/e2eBridge.ts** Adds deterministic metadata controls for runtime visual and navigation tests. **desktop/tailwind.config.js** Makes semantic disabled colors available to chip styles. **desktop/tests/e2e/entity-link-recipient-cards.spec.ts** Verifies entity labels, project-aware tooltips, delayed metadata, and navigation behavior. **desktop/tests/e2e/navigation.spec.ts** Verifies channel and message chip states, metadata resolution, deletion handling, and navigation. **desktop/src/shared/ui/tooltip.tsx** Migrates the shared tooltip surface from the primary color pair to the semantic secondary pair. It also sets Radix `disableHoverableContent` and makes tooltip content pointer-transparent so leaving the trigger dismisses the tooltip predictably. **desktop/src/features/agents/ui/RestartDiffBadge.tsx** Aligns restart-diff tooltip values and supporting copy with the secondary tooltip foreground. **desktop/src/features/channels/ui/AddChannelBotTeamsSection.tsx** Aligns rich team tooltip descriptions, persona chips, avatars, and names with secondary tooltip semantics. **desktop/src/features/projects/ui/ProjectAuthorIdentity.tsx** Uses the matching secondary tooltip foreground for project author metadata. **desktop/src/features/projects/ui/ProjectCards.tsx** Aligns repository-unavailable tooltip descriptions with the secondary tooltip foreground. **desktop/src/shared/styles/globals/utilities.css** Makes huddle tooltips consume their dedicated palette directly without misleading primary or secondary aliases. **desktop/src/shared/styles/globals/tooltipSemantics.test.mjs** Pins direct huddle-token consumption and rejects semantic alias drift. **desktop/tests/e2e/tooltip-semantics.spec.ts** Verifies simple and rich tooltip surfaces and descendant tokens in Buzz light and Catppuccin Mocha dark themes. **desktop/playwright.config.ts** Registers tooltip semantic coverage in the desktop smoke suite. </details> ## Reproduction steps 1. Run the desktop app and open a message containing Buzz links for a channel, message, repository, issue, and pull request. 2. Confirm message links show only the channel name and issue links show only the repository name inline, with no fetched content/title or event hash; confirm other native links remain compact rather than rendering raw identifiers or expanded previews. 3. Hover the chips and confirm the neutral secondary tooltip adds concise context without duplicating the visible label. 4. Open each chip and confirm it follows the same channel, message, repository, issue, or pull-request navigation as before. 5. Inspect links while metadata is loading, unavailable, or definitively deleted and confirm the fallback remains compact, legible, and navigable where appropriate. 6. In Buzz light and Catppuccin Mocha dark themes, compare a simple channel-members tooltip and the rich Add agents team tooltip; both should use secondary semantics with readable nested content. 7. Move the pointer from a trigger toward its tooltip; confirm the tooltip dismisses rather than remaining open as hoverable content. ## Screenshots The matrix below covers both the default light theme and Catppuccin Mocha with an orange accent. Message/issue-chip rows affected by the stable-label pivot are temporarily omitted until they can be recaptured from the real built UI; the prior images showed the obsolete expanding inline label. | State | Default light | Dark · orange accent | |---|---|---| | Public stream channel tooltip |  |  | | Private forum channel tooltip |  |  | | Public forum channel tooltip |  |  | | Channel references, including unresolved |  |  | | Unknown channel permalink |  |  | | Archived channel tooltip |  |  | | Repository entity |  |  | | Pull request entity |  |  | | Entity metadata fallback |  |  | ### Tooltip semantic surface spot check | Buzz light | Catppuccin Mocha dark | |---|---| |  |  | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
569308c23c |
fix(desktop): restore recent channel sorting (#6402)
## Summary - batch desktop last-message queries into groups of 128 so workspaces with larger channel counts stay within the relay's explicit-channel limit - propagate timestamp query failures instead of replacing the sidebar's cached recency with all-null data - advance channel recency from live message events so Recent ordering updates without waiting for the next refresh ## Root cause The desktop sent one explicit `#h` filter per channel in a single `/query`. The relay rejects a request with more than 128 aggregate explicit channel values. The desktop swallowed that rejection and returned no timestamps, so Recent correctly fell back to A–Z for every channel. ## Testing - `cargo test --manifest-path desktop/src-tauri/Cargo.toml last_message_filters_stay_within_relay_channel_cap --lib` - `cd desktop && pnpm test` (5,132 passed) - `cd desktop && pnpm exec biome check src/features/channels/useLiveChannelUpdates.ts src/features/channels/lib/channelRecency.ts src/features/channels/lib/channelRecency.test.mjs tests/e2e/channel-sort.spec.ts` - `cd desktop && pnpm check:file-sizes` - `cargo fmt --all -- --check` - `cd desktop && pnpm build:e2e` followed by `pnpm exec playwright test tests/e2e/channel-sort.spec.ts --project=smoke --workers=1` against a dedicated static server (4 passed) ## Manual test In a workspace with more than 128 channels, choose **Channels → Sort → Recent**. Channels should order by latest message instead of A–Z. While Recent is selected, a new message in a visible channel should move that channel to the top immediately. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
06e8be9098 |
fix(desktop): make reconnect repair lossless (#6415)
## Summary - repair exact-channel reconnect gaps from a source-coupled 1,865-second floor (`900` seconds of relay future tolerance + `960` seconds of DB created-at floor + `5` seconds of fence margin) - page the existing authenticated `POST /query` bridge losslessly with `(until, before_id)`, including dense timestamp boundaries and an unbounded first page that does not trust the renderer clock - keep the restored live REQ unchanged while deduplicating live/repair overlap until both live EOSE and repair completion for the active connection generation ## Scope and behavior The wider repair applies only to live subscriptions containing the exact channel-event kind set and one `#h`. Profile, read-state, global, channel-less, and other subscription behavior is unchanged. Failed or stale repairs retain their pinned lower bound for the next reconnect. This intersects the replay path described in #3215, but does not claim to fix that issue. Desktop already has a bounded notification-side event-ID guard; this PR adds transport dispatch dedupe for the restored-live/repair overlap. Notification behavior should remain a focused regression check while this wider repair window rolls out. ## Testing - `cd desktop && pnpm typecheck` - `cd desktop && pnpm biome check src/shared/api/channelReconnectRepair.ts src/shared/api/relayClientSession.ts src/shared/api/relayClientShared.ts src/shared/api/relayClosedRecovery.ts src/shared/api/relayReconnectReplay.ts src/shared/api/relayReconnectReplay.test.mjs src/testing/e2eBridge.ts` - `cd desktop && node --test src/shared/api/relayReconnectReplay.test.mjs` (26 passed) - `cargo test --manifest-path desktop/src-tauri/Cargo.toml channel_reconnect_repair` (2 passed) - pre-push hooks passed at `743283439eb6be7969e19951cbb6720c5e493a04` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
54bbe7305b |
perf(desktop): resolve references without directory scans (#6328)
## Summary - keep the channel poll membership-only and fetch the open-channel directory only from explicit discovery surfaces - resolve unknown ID-bearing references through one bounded `get_channel_details` lookup, while name references remain limited to member channels plus a browse/search-warm directory cache - fail closed for fetched private metadata and cover cold lookup, warm/member hits, private visibility, and cached misses with mounted hook contracts --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> |
||
|
|
1934e83bf5 |
feat(workflows): add workflow editor (#6248)
**Category:** new-feature **User Impact:** Users can create, edit, duplicate, and deep-link to workflows in a responsive visual editor without losing unsupported YAML or unsaved work. **Problem:** Workflow editing was split across disconnected surfaces and lacked reliable URL state, lifecycle protection, and parity between Form and YAML modes. **Solution:** This adds a route-addressable editor foundation with stable pane identity, guarded dirty exits, lossless Form/YAML transitions, responsive workflow and channel controls, and matching reaction-filter execution support. <details> <summary>File changes</summary> **crates/buzz-workflow/src/lib.rs** Apply reaction trigger filters during workflow execution and cover target-message gating. **crates/buzz-workflow/src/schema.rs** Extend the reaction trigger schema with the editor-owned filter field. **desktop/src/app/navigation/useAppNavigation.ts** Add navigation helpers for explicit workflow create, edit, and duplicate editor modes. **desktop/src/app/routes/WorkflowsRouteScreen.tsx** Coordinate route state with the shared workflow library and editor dialog. **desktop/src/app/routes/lazyWorkflowsRouteScreen.ts** Share one lazy route component across workflow route entry points to avoid loading flashes. **desktop/src/app/routes/workflows.$workflowId.tsx** Parse workflow editor modes and pane deep links for workflow-specific URLs. **desktop/src/app/routes/workflows.tsx** Parse library-level create state and render the shared workflow route screen. **desktop/src/app/AppWorkflowEditorOverlayProvider.tsx** Host the shared workflow editor at the app-shell level so channel-originated workflow dialogs stay above the active channel instead of replacing it. **desktop/src/shared/context/WorkflowEditorOverlayContext.tsx** Expose route-independent open-existing and create-new workflow actions to channel settings. **desktop/src/features/workflows/ui/WorkflowEditorHost.tsx** Share editor loading, unavailable, and dialog lifecycle wiring between canonical workflow routes and the channel overlay. **desktop/src/features/channels/ui/ChannelManagementSheet.tsx** Add an experiment-gated, Canvas-style Workflows ingress below Canvas, including channel-scoped loading, error, empty, and list states plus open/create actions; disabled users issue no workflow query. **desktop/src/features/channels/ui/ChannelWorkflowsSection.tsx** Render the channel workflow list and New workflow action without pushing the existing settings sheet past its file-size ceiling. **desktop/src/features/workflows/ui/ChannelCombobox.tsx** Adopt the final channel presentation, portalled scrolling, and one-shot create-flow opening behavior. **desktop/src/features/workflows/ui/CreateWorkflowDialog.tsx** Remove the superseded create-only dialog in favor of the unified workflow editor. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Open workflow cards in the detail and run-history modal while preserving explicit edit and duplicate actions. **desktop/src/features/workflows/ui/WorkflowDetailDialog.tsx** Present workflow Trigger/Steps in the shared modal chrome, with top-chrome ingress to a responsive right-side run-history inspector and an explicit edit action. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Unify create, edit, and duplicate lifecycle handling with URL panes, generated-name synchronization, dirty-exit guards, stale-write preservation, and protected webhook-secret handoff. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Build the responsive Form/YAML editor shell, stable step selection, insertion and removal behavior, and lossless canonical-YAML synchronization. **desktop/src/features/workflows/ui/WorkflowStepCard.tsx** Align workflow step controls and presentation with the final editor interaction model. **desktop/src/features/workflows/ui/WorkflowUnavailableDialog.tsx** Show a non-disclosing loading or unavailable state for missing and inaccessible workflow links, with retry and close actions. **desktop/src/features/workflows/ui/WorkflowWebhookSecretDialog.tsx** Obscure one-time webhook secrets by default and require explicit confirmation before any close or navigation discards them. **desktop/src/features/workflows/ui/WorkflowsScreen.tsx** Connect library state and workflow actions to the route-addressable editor. **desktop/src/features/workflows/ui/WorkflowsView.tsx** Restore the responsive workflow library, create tile, cards, loading states, and shared action menu. **desktop/src/features/workflows/ui/workflowEditorPane.test.mjs** Cover pane parsing, serialization, and stable step-ID reconciliation. **desktop/src/features/workflows/ui/workflowEditorPane.ts** Define explicit trigger and stable step pane URL state. **desktop/src/features/workflows/ui/workflowYamlDocument.ts** Read and update header fields independently of full form validation so incomplete steps cannot clear or disable the workflow title. **desktop/src/features/workflows/ui/workflowYamlDocument.test.mjs** Cover document-level workflow header reads and writes for incomplete definitions. **desktop/tests/e2e/workflow-title-stability.spec.ts** Verify generated, renamed, saved, and duplicated titles remain stable while moving between trigger and step panes. **desktop/src/features/workflows/ui/workflowFormTypes.test.mjs** Cover lossless Form/YAML round trips and actionable fallback for unsupported fields. **desktop/src/features/workflows/ui/workflowFormTypes.ts** Own canonical workflow YAML conversion while preserving supported trigger and step fields. **desktop/src/shared/ui/PortalledScrollArea.tsx** Provide bounded scrolling for popovers rendered outside their dialog container. **desktop/src/shared/ui/popover.tsx** Allow workflow popovers to use the shared portalled scroll container. **desktop/tests/e2e/channels.spec.ts** Cover disabled and enabled Workflows experiment states, including suppressed queries while disabled, placement beneath Canvas, channel workflow listing and opening, channel-preselected workflow creation, and direct return to the channel Workflows panel after close, discard, or cancel. **desktop/tests/e2e/workflows.spec.ts** Exercise library actions, deep links, create/edit/duplicate lifecycle, dirty exits, responsive editor behavior, YAML safety, stale updates, and one-shot channel selection. </details> ## Reproduction steps 1. Open **Workflows** and confirm the responsive card library, create tile, card action menu, and card-to-detail/run-history modal navigation. 2. Open `?view=create`; confirm the channel chooser opens once, the trigger inspector stays hidden until a channel is selected, and closing the chooser does not make it reopen after unrelated edits. 3. Create a workflow, switch between Form and YAML, add and remove steps, refresh a pane deep link, and confirm the selected trigger or stable step remains addressable. 4. Edit or duplicate a workflow, make an unsaved change, and confirm close, Escape, browser navigation, and route target changes require discard confirmation while pane-only navigation does not. 5. Enter unsupported YAML and confirm Form mode gives an actionable fallback without rewriting the definition; verify reaction triggers preserve and execute their filter. 6. Open a channel’s settings, select **Workflows** below **Canvas**, and open or create a workflow; confirm the shared modal stays over the channel, the channel URL does not change, New workflow preselects that channel, and closing or discarding returns directly to the channel’s **Workflows** panel. 7. In create, edit, and duplicate modes, move between the trigger and incomplete step panes and confirm the generated or edited title remains visible and editable. ## Screenshots Fresh captures from product head `c5c3abc91a71fe511d43e6cc9168b1626d0c217c`; the later review-guidance fix does not alter these pictured states. ### Workflow library and actions  ### Workflow editor — wide  ### Workflow editor — narrow inspector overlay  ### Workflow editor — active channel overlay  ## Review feedback addressed - Corrected Diff Posted condition guidance to use the executor-supported `str_contains(trigger_text, "deploy")` syntax and added a visible Playwright regression assertion. - Scoped direct workflow-detail navigation coverage to the stable `Edit workflow` dialog name, asserted the workflow title separately, and retained the trigger-node assertion. - Routed dirty channel-overlay Duplicate/Edit transitions through the existing discard confirmation, preserving the original YAML draft when the user keeps editing. - Made workflow deletion await relay success, remain non-dismissible while pending, and retain the confirmation/editor/draft with an actionable inline error on rejection. - Gated the channel-settings Workflows ingress and channel workflow query behind the `workflows` experiment, with defensive rendering if the flag changes while that view is active. - Matched the Reaction Added trigger to the prototype reaction picker, preserving native/custom/legacy values in canonical YAML and providing an explicit clear action. - Kept a channel’s Workflows panel mounted beneath channel-origin editors so clean close, dirty discard, and create cancel return directly to that panel without changing the channel URL. ## Verification Verified at exact pushed head `76ebba7b7ace1e13445744200da70ca9da231b7e`: - Push hooks passed: destination-org policy, branch-skew, differential file-size, Desktop checks, TypeScript typecheck, and **5,115/5,115** Desktop unit tests - Focused channel lifecycle E2E passed: dirty edit discard returns directly to the channel Workflows panel; create cancel does the same while preserving the channel URL and preselected channel - E2E production build, standalone TypeScript typecheck, and Biome checks on both touched files passed - Reaction-picker regression spec remains recorded at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **5/5 passed** (picker interaction, canonical YAML persistence/clear, legacy-value preservation, save/reopen round trip, narrow viewport containment) - Blox existing workflow E2E regression set at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **36/36 passed** - Earlier blocker and review regressions remain recorded at `3760c3d657a525f5af98e8d0f98bdd03999d8e61`: **5,114/5,114** Desktop unit tests and the dirty-overlay/deletion focused checks - Working tree clean; local branch, remote branch, and PR head all match the exact SHA above ### Related issue None found. Closest prior work: #231. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
2b7be9340d |
feat(desktop): make Projects workspaces selectable (#6368)
## Summary Projects workspaces now support consistent selection across projects, repositories, reviews, tasks, and channel results. A user can collect related entities, see the active selection without losing their current workspace, and pass a bounded, injection-safe representation to an agent for discussion. Detail views now keep repository source and availability explicit while preserving contextual actions across local and remote workspaces. Missing or inaccessible repositories produce a recoverable state instead of leaving the workspace ambiguous. This is part 2 of the Projects v6 stack. Parts 3 and 4 will add context-aware collaboration and navigation/detail-page polish. ### Related issue Related: #6335 ### Testing - Full pre-push gate passed: file-size checks, Biome, TypeScript, Desktop unit tests, Tauri checks, and Rust tests - Project context safety suite: 9/9 passed, including bounded untrusted overview data and hidden-context stripping - Updated Playwright coverage exercises grouped selection, selection-aware agent context, repository source states, and detail actions --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
9c2f05346f |
fix(desktop): show complete repository trees (#5102)
## Summary Large repositories were silently limited to the first 250 lexicographically sorted paths, which could leave the Files tab showing only dot-directories. Retain all tracked paths while limiting eager content previews, and replace the Files tab hard cutoff with incremental 200-entry pagination. ### Related issue Fixes #4960. No duplicate PR found. ### Testing - Targeted Rust regression: `cargo test --manifest-path desktop/src-tauri/Cargo.toml parse_ls_tree_keeps_paths_after_eager_preview_limit --lib` - Pagination helper regression: `node --import ./test-loader.mjs --experimental-strip-types --test src/features/projects/lib/projectsViewHelpers.test.mjs` - Desktop Biome, type checking, and file-size checks pass - All 4,393 desktop tests pass - Repository pre-push hooks pass - `just ci` currently stops on pre-existing Clippy warnings in `crates/buzz-acp/src/queue.rs` and `crates/buzz-agent/src/hints.rs`, outside this PR diff Generated with Codex --------- Signed-off-by: Evan Chen <evanc@squareup.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
bbd20fae75 |
perf(desktop): move five hot renderer paths from JS into Rust (#6024)
## Summary
Moves five hot renderer paths out of JavaScript and into Rust, targeting
main-thread CPU on the desktop app: wakeups, IPC round trips, and
per-frame JS work during reconnect storms, catch-up, and archive sync.
Five commits, reviewed and ratified sequentially (each round included
independent re-review, mutation-tested coverage, and full-suite gates at
pinned heads):
| Commit | Change |
|---|---|
| `9b5a82ab9` | Batch inbound relay frames in the native websocket
plugin: N run-loop wakeups collapse into 1 per 8ms window, byte-bounded
so no batch crosses the Tauri direct-execute threshold onto the slow
fetch path. NIP-42 AUTH bypasses the window. |
| `8b047a35e` | Move the local-archive subscription into Rust: the
renderer no longer sees archived frames at all. Shared
`native_relay_client` (one authenticated socket per relay+pubkey,
declarative subscription reconciliation, CLOSED-driven retry with
write-time eviction), lease/epoch lifecycle ownership so delayed IPC
cannot resurrect a stopped task. |
| `609962b49` | Move persona catalog fetching into Rust: signature
verification (~473us/event, ~0.24s per 500-event page formerly on the
webview thread) now runs under `spawn_blocking`; one projected DTO
crosses IPC. |
| `38afea0a0` | Batch unread catch-up behind one IPC call: N per-channel
REQs/awaits become one command with global two-pass classification,
making the completion-order race unrepresentable. Both IPC DTO surfaces
pinned by wire tests asserting serde output against the TS contract. |
| `b955f528b` | Move observed unread state into native SQLite: scoped
WAL DB with a sequence/revision protocol, transactional localStorage
migration, epoch-tagged rebuild detection. Membership no longer
serializes five capped arrays per catch-up: measured 327.4 KiB before,
0.2 KiB after at the same populated 5x1000 fixture. |
Full design rationale, failure matrices, and verification detail are in
the individual commit messages.
### Known behavior notes
- **Native migration is a one-way door per profile.** A successful
native open sets `migration_complete` in the first open's transaction
and the renderer then deletes the legacy localStorage key
(`useObservedUnreadPersistence.ts:290-294`,
`observed_unread.rs:420-442`). The one-release JS fallback remains for
native open *failure*, but a profile that has completed migration cannot
return to the JS store. Relevant if a rollback path is ever needed.
- **The membership seed is built unconditionally at startup**
(`useUnreadChannels.ts:245-250`), before any `isNative()` check. It is
ref-cached and small, but it is a startup cost the baseline does not
pay, including on the fallback path.
### Related issue
None found. Work originated and was reviewed in the Buzz
`buzz-gui-performance` channel.
### Testing
**Current head is `9a8128ba4d2d695113505162be388fcec62cda0a`. For
receipts at that head, see [Round-3
receipts](#round-3--final-head-9a8128ba4) at the bottom; the block
immediately below is the historical record for the original five-commit
pack and its counts are superseded.**
The receipts in this subsection are at exact pack head
`b955f528b6dfa34db089b21cc718fba0f9284d6f` (tree oid `dcc711a66`),
verified by three independent instruments (implementer, reviewer,
gatekeeper), with `git rev-parse HEAD` confirmed in the same shell as
each run:
- `cargo test --lib` (full package): **2511 passed / 0 failed** (+7 +3
in the other targets)
- `pnpm test`: **4920 / 4920 across 74 suites**
- `cargo fmt --check`, `clippy --all-targets -D warnings`, `tsc
--noEmit`, `pnpm check` (including the file-size ratchet): all clean
- Mutation controls on the final review round: badge-lane controls A and
B each turn exactly one test red; seed-guard (M4) and
anchor-monotonicity (M5) mutants each killed by exactly their own
witness; restored bytes fully green
- The observed-unread native path is exercised by an in-tree
`__TAURI_INTERNALS__` protocol rig (entry, replay, gap, rebuild,
markers, membership, badge lane), added after review found the original
suite never left the localStorage fallback
**Open gates, stated plainly (status as of head `9a8128ba4`):**
- **Paired CPU benchmark still not run.** The three-arm design (baseline
`78cbffeb6` vs pack vs a null arm with native forced off, fresh profile
per arm) was instrument-reviewed but never executed. The IPC payload
reduction above is measured; end-to-end CPU improvement is predicted,
not measured. This gate is unchanged since `b955f528b`.
- **Live-local gate at the final head: GREEN at `9a8128ba4`.** A real
native Tauri live-local pass per `TESTING.md` was run at this exact
head. Receipts:
`.scratch/pr6024-final-9a8128ba4d2d695113505162be388fcec62cda0a/`,
`MANIFEST.txt` `start_utc=2026-08-17T21:58:07Z`, `FINAL_ATTESTATION.txt`
`end_utc=2026-08-17T22:20:47Z` — both after the commit's `21:32:51Z`,
and the build worktree's HEAD is `9a8128ba4` with a clean tree. **I
opened these files rather than relaying the summary**, and recomputed 6
SHA256SUMS entries against the sealed manifest (6/6 match). From the raw
artifacts: historical catch-up persisted 10 seeded events into the
native `observed-unread.db`; a realtime event advanced it 10→11; the
relay was killed and restarted with Desktop up and an event sent after
recovery advanced it 11→12 within 2 polling ticks; Desktop was then
stopped, an event sent offline, and after relaunch with the appdir
preserved catch-up advanced it 12→13. `relay.log` shows 2 app WebSocket
connections, the restart-close signal, and fresh connections after each
recovery — the app-attributable socket evidence the previous round
lacked. The preserved appdir database is still on disk and `select
count(*) from observed_events` returns **13**. The ignored real-relay
archive wire test passed **1/1** at this head. This exercises the
`lib.rs` / `e2eBridge.ts` merge recombination under a real app and
blocker 2's failure-mode family (relay killed mid-session, app killed
with offline traffic in flight, sync did not go dead). It does **not**
directly force the precise overlapping B/C archive-ownership timing
window through the GUI; that invariant is pinned by the compiled
`drop(owner)` mutation proof below, and is deliberately not relabelled
as live race evidence. **Retraction retained for the record:** an
earlier revision of this description said live-local was GREEN at
`9a8128ba4` while citing a run executed against `f079d0914`, the pre-fix
head, roughly three hours before `9a8128ba4` existed. That claim was
false and was corrected before this run existed. The GREEN above rests
on the new receipts only.
- **Workspace-wide `cargo test --workspace` is red on `main`, not on
this PR.** The exact-head live run's workspace logs contain honest
`rc=101` results. All are in crates this PR does not touch: `git diff
origin/main...9a8128ba4` is **empty** for `crates/git-sign-nostr`,
`crates/buzz-relay`, and `crates/buzz-pair-relay`.
`git-sign-nostr::tests::test_parse_envelope_rejects_invalid_oa_pubkey`
fails **deterministically on clean `main`** — reproduced independently
in fresh worktrees at merge-base `a282e0643` and at current tip
`7f61cf431` (55 passed / 1 failed, same assertion at `lib.rs:2136`).
Root cause: `nostr` 0.36→0.44 changed `PublicKey::from_hex` from a
curve-point parse to a plain hex decode, so an all-zeros (off-curve) key
now parses; measured with a standalone probe, `from_hex("0"*64).is_ok()`
is `false` on 0.36.0 and `true` on 0.44.7. Filed as block/buzz#6175,
with the observation that nothing in CI runs this crate's tests (`just
test-unit` enumerates packages and omits it; `server-cross-compile`
compiles it and runs nothing). The mesh-demo and pair-relay failures in
the same logs are timing flakes in equally untouched crates. **None of
these gate this PR.**
- **Full smoke + integration: GREEN at `9a8128ba4`.** CI run 32072147445
is `success` with every job green — all 4 Desktop Smoke E2E shards, both
Desktop E2E Integration shards, Desktop Core, Desktop Build (macOS),
Desktop E2E Relay, Rust Lint, Windows Rust, DCO, Dead Token Reference
Guard. Attempt 1 was cancelled by an infrastructure hang, not by this
code: both integration shards stalled 19m37s inside `Install Playwright
system dependencies` (an apt install that runs before the e2e bundle is
built or the relay starts), so no test executed and all downstream steps
were skipped. The same step took 16s at `9128b9389` and 12s/18s on
attempt 2 of these same bytes. Attempt 2 was a job rerun, not a push;
the head is unchanged.
---
Opened by Eva (agent) on Tyler's behalf at his request.
---
## Review round 2 — head `9128b9389`
Carl's review
[4953970089](https://github.com/block/buzz/pull/6024#pullrequestreview-4953970089)
raised two blockers against `f079d0914`. Both were confirmed real at the
source by a second reader and both are fixed in `9128b9389`.
**Blocker 1 — recovery dropped the mutation whose ingest failed.** Every
captured mutation now gets one retry after `reopen()` refreshes
sequence/revision, which is safe because ingest is idempotent (events
upsert `DO NOTHING`, channel latest advances by `MAX`, membership is
`INSERT OR IGNORE`, and a replayed sequence returns a snapshot rather
than reapplying). `snapshotRequired` takes the same recovery path
instead of being treated as success. If reopen and retry both fail,
native is explicitly degraded and equivalent state is applied to the JS
fallback so `isNative()` stops reporting healthy. Regressions assert the
*originally rejected* marker, destructive clear, and membership delta
each survive; removing the retry takes 5 tests red including all three.
**Blocker 2 — a superseded archive start could tear down the newer
scope's relay session.** `begin` now returns an `ArchiveOwnership` token
that holds both ownership guards, and `archive_session` requires one, so
session acquisition happens inside the ownership critical section rather
than after it.
### Deviation from the requested regression test for blocker 2
Carl asked for "a production-wiring regression that pauses B after
`begin`, fully installs C, resumes B, and proves only C's session and
subscriptions remain live." **We did not write that test, deliberately,
and wrote a stronger one instead.**
Under this fix that scenario is unreachable by construction. The fix is
not a revalidation check that a test could observe passing or failing —
it is a type-level fence: `begin` hands the winner a token that holds
the ownership locks, and `archive_session` cannot be called without one.
A test that staged "B pauses after `begin`, C fully installs" would be
staging a state the type system now forbids, so it could only pass
vacuously.
Two things close the blocker, and only one of them is a test:
1. *A superseded start cannot reach `archive_session` at all.* Enforced
by the compiler, not by a test. `ArchiveOwnership` is un-constructible
outside `archive::sync`. Verified by attempting both bypasses: calling
`archive_session` without a token fails **E0061**, and forging the token
from a real non-archive caller fails **E0451** ("fields `_latest` and
`_running` are private"), with a second independent barrier because
`RunningSync` is itself a private type.
2. *While an owner holds the token, no newer start can claim.* This is
the property the token's usefulness rests on, and it is what the new
regression pins —
`a_newer_start_cannot_claim_while_the_owner_holds_its_token`.
That test is aimed at the one mutant that actually threatens this
design: keeping the token but releasing the guards inside `begin`, which
is what someone reaches for to avoid holding a lock across an await. It
**compiles**, it keeps every other lifecycle test green, and it restores
the exact race. Against it the new test goes **single-red** — the other
26 tests in the module stay green.
Proving mutual exclusion over the whole claim → acquire → install unit
is strictly stronger than staging a pause the type system forbids, which
is why we chose it.
Also recorded in the code, in `ArchiveOwnership`'s doc comment: what
this token does **not** cover. It serializes archive lifecycle against
archive lifecycle, not against a plain `NativeRelayClient::session()`
call from a non-archive feature. That window predates this PR and is
theoretical today given renderer gating, so it is documented rather than
fixed here.
### Round-2 receipts at `9128b9389`
- `cargo test --lib`: **2513 passed / 0 failed**
- `pnpm test`: **4926 / 4926**
- `cargo clippy --all-targets -D warnings`, `cargo fmt --check`, `pnpm
check`: rc 0
- Merged against `origin/main` in a scratch worktree (clean, zero
conflicts) and re-gated on that merged tree: **2544** Rust / **4956**
frontend, both 0 failed
---
## Round 3 — final head `9a8128ba4`
`9a8128ba4d2d695113505162be388fcec62cda0a` is a merge of `origin/main`
(`a282e0643`) into the pack branch, taken **before** final verification
so that verification would run on the bytes CI builds rather than on a
head that was about to move.
The merge is textually clean but touches two files the pack also
touches, `desktop/src-tauri/src/lib.rs` and
`desktop/src/testing/e2eBridge.ts`, so recombination was checked
explicitly rather than inferred from the absence of conflicts: **all 342
pack-added lines in those files survive (0 missing), and all 17
main-added lines survive (0 missing)** — no silent revert in either
direction. Main's contribution is a `#[doc(hidden)] pub use
print_agent_access_owner_only_probe_if_requested` in `lib.rs`, plus a
Bumble→Pollen rename and workflow revision / `workflowUpdateError` mocks
in `e2eBridge.ts`.
### Receipts at `9a8128ba4`
Implementer, in a shell with `git rev-parse HEAD` confirmed at this SHA:
- `cargo test --lib` (full package): **2544 passed / 0 failed** / 18
ignored
- `pnpm test`: **4956 / 4956** across 74 suites
- `cargo clippy --all-targets -D warnings`, `cargo fmt --check`, `pnpm
check`: rc 0
- The 4 remaining `pnpm check` findings (`channelMutesStorage.test.mjs`,
`channelStarsStorage.test.mjs`, `terminal.css`,
`empty-edit-delete.spec.ts`) are all in files this PR never touches
- All 7 pre-push hooks green, including `branch-skew`, which now passes
on its own because the branch contains `origin/main`
Independent reviewer, same exact head, own instruments:
- `cargo test --workspace`: green; `cargo clippy --workspace
--all-targets -- -D warnings`: green *(a later workspace run at
`9a8128ba4` hit pre-existing `main`-side failures in untouched crates —
see the workspace-gate entry under Open gates above; the difference is
flake timing plus which run happened to reach `git-sign-nostr`, not a
change in this PR)*
- `pnpm test`: **4956 / 4956**; `pnpm check`: rc 0, same known findings
outside touched files
- **Mutation proof of the blocker-2 fix:** dropping the ownership
token's hold (`drop(owner)`) turns
`a_newer_start_cannot_claim_while_the_owner_holds_its_token` red;
restoring the shipped bytes returns the suite green. The invariant that
a superseded start cannot claim or disturb a newer archive session is
pinned by a test, not just by the type system.
- **Live-local at this exact head: attested and verified.** The
mis-cited `f079d0914` run that originally appeared here is retracted; a
real run at `9a8128ba4` replaced it, and its receipts were opened
file-by-file (not relayed) by a second agent. See the live-local entry
under Open gates above for the receipt path, wall-clock bounds,
store-count chain, and socket evidence.
- Verdict: minimalness **9/10**, elegance **9/10**, correctness
**9.5/10**. **Score restored** now that the exact-head live-local run
exists and its provenance has been checked from the files (SHA-named
receipt dir, self-dating manifest, receipt mtimes after the commit time,
sealed digests spot-checked). Gatekeeper re-bless at `9a8128ba4`:
**9/10, merge recommended.**
The paired CPU benchmark remains the one gate not run. It is a
measurement of the pack's *benefit*, not of its correctness, and it is
stated as unrun rather than estimated.
---------
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
|
||
|
|
87f8ff82ae |
feat(desktop): refine repository-aware project workspaces (#6003)
## Summary - Makes Projects repository-aware across browsing, branch and tag selection, local and remote source management, work items, commits, and contextual repository actions. - Refines project details into consistent single-column reading surfaces with a resizable, section-aware context pod, while keeping project conversations available in the attached chat panel. - Adds persistent sidebar project navigation, direct entity links, repository discussion channels, contributor identity matching, and consistent loading and activity presentation. - Keeps branch-specific controls on code-oriented sections while Tasks, Reviews, and Channels remain repository-scoped, reducing misleading context and actions. Replacement for #5981 with an identical final tree flattened into one signed-off commit because the required DCO check suite remained stalled. ### Related issue N/A ### Testing - [x] Desktop pre-push checks, TypeScript typecheck, and unit tests - [x] E2E production build with `pnpm build:e2e` - [x] Focused Playwright smoke coverage for project workspace, task, review, commit, sidebar, and contextual-panel behavior - [ ] Attach final before/after screenshots after design review --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz> Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz> |
||
|
|
6e8d078ffe |
fix(desktop): emit camelCase config-write payload fields (#6062)
Fixes #6015. `ConfigWriteMechanism` is internally tagged and carried only `rename_all = "camelCase"`. On an internally tagged enum that renames the **variants**, never the variants' fields, so the payload went out snake_case: ``` {"type":"respawnWithEnvVar","env_key":"K"} {"type":"acpSetConfigOption","config_id":"c"} {"type":"acpSetSessionModel"} {"type":"gooseNativeConfigWrite","config_key":"g"} {"type":"readOnly"} ``` against `envKey` / `configId` / `configKey` in `desktop/src/shared/api/types.ts:615-620`. That output is a probe run of the real module before the fix, not a reading of the code. What makes it read as correct is the asymmetry: the variant *names* rename fine, so the `type` discriminant and every `switch (writeVia.type)` behave; and the enclosing `NormalizedField`'s own fields (`writeVia`, `overriddenValue`, `isRequired`) rename fine too, because `rename_all` does apply to struct fields. Only the variant's field is wrong. Adding `rename_all_fields = "camelCase"` fixes it. `rename_all_fields` appeared zero times in `desktop/src-tauri` before this. **Severity, stated plainly: latent, not currently user-visible.** Nothing in `desktop/src/**` reads `.envKey`/`.configId`/`.configKey` off a `writeVia` — `AgentConfigPanel.tsx` is the only `RuntimeConfigSurface` consumer and never touches the field, and no Rust code deserializes the type either. The write-back path these fields exist for is not wired yet. The hazard is for whoever wires it: `invokeTauri<T>` is an unchecked cast, so they get `undefined` with a green `tsc`. I also carried the attribute onto `ConfigFieldType`. Its only payload field is `options`, single-word, so that half is not a fix — it is the attribute the next multi-word field would silently need. One divergence from the issue's suggested step 3: the 20 `e2eBridge.ts` sites already emit camelCase, and camelCase is the contract, so they are correct as written — changing them would have been wrong. What they lacked was provenance, since agreeing with `api/types.ts` while the backend emitted something else is exactly what let this sit. They now name the Rust test that pins the bytes. **Tests** (`wire_format_tests`, 4 cases, whole-value not key-set — a key-set assertion still passes when a variant name regresses): - every variant against the TypeScript spelling; - the nested `NormalizedField`, which is the shape the renderer actually receives; - a camelCase round-trip **plus** an assertion that the old `env_key` spelling is now rejected, so a revert cannot quietly keep deserializing; - `ConfigFieldType::Enum`. Removing `rename_all_fields` again turns three of the four red. Verified locally: full Tauri library suite 2444 passed / 15 ignored / 0 failed; `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warnings` clean; `cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --check`; in `desktop/`: `pnpm typecheck`, `pnpm check`, `pnpm test` 4954 passed; `git diff --check`. Not run: the app itself — there is no UI path to this field yet, which is the same reason the bug is latent. --------- Signed-off-by: Taksh <takshkothari09@gmail.com> |
||
|
|
6ea7a2b221 |
fix(desktop): preserve early relay auth challenges (#3320)
## Summary - buffer native WebSocket frames until `plugin:websocket|connect` returns the socket ID and the NIP-42 auth waiter is installed - drain those frames in order before normal inbound handling begins - add a deterministic E2E seam and regression for an AUTH challenge delivered before connect resolves ## Root cause The native WebSocket task starts forwarding relay frames before the connect command returns its socket ID. Buzz relay sends the NIP-42 AUTH challenge immediately on connection, so Desktop could process that challenge while `wsId` and `authRequest` were still unset. The challenge was discarded, the cold connection failed authentication, and the first plain-text send failed. Channel history still appeared because it loads through the Tauri channel-window command rather than this frontend WebSocket. ## Verification - deterministic regression is red before the fix (`connecting` after 5s) and green after it - `pnpm test`: 3,721 passed - `pnpm build:e2e`: passed - focused early-AUTH E2E: passed - existing failed-initial-dial E2E: passed - `pnpm typecheck`: passed - Biome + file-size pre-commit checks: passed The complete 7-test relay-reconnect file passed once before the final line-count-only compaction. After that compaction, two full-file reruns each had the same unrelated startup-seam flake in the existing initial-dial test; both the new test and that existing test pass independently at the committed HEAD. --------- Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> |
||
|
|
3fdf289b78 |
fix(desktop): bound remote agent mention authorization (#6224)
## Summary - add a targeted native `revalidate_relay_agents(pubkeys, channel_id)` command for send-time authorization - scope membership discovery to the destination channel and selected pubkeys before runtime/profile/policy queries - replace full relay-directory rebuilds with targeted checks before agent side effects and again at publication - preserve managed-agent evidence independently and retain internal owner-only filtering ## Security and trust boundary The targeted command reuses the existing authoritative chain: 1. relay-signed kind:39002 membership scoped by viewer and destination `d` tag 2. agent runtime directory event 3. agent-signed owner profile verification 4. owner-signed managed policy 5. internal-build `owner_only` filtering before policy lookup and on final results Relay-only agents are dropped on any targeted directory failure. Fresh managed-agent evidence remains valid when the unrelated relay directory fails. ## Validation - pre-push gate passed: branch skew, Desktop check/typecheck/tests, Tauri checks, Rust tests, and mobile tests - Desktop unit suite: 4,992 passed - targeted Rust relay-directory tests passed - focused Playwright mention-send acceptance passed - relay-only send emits the expected `p` tag - exactly two targeted `revalidate_relay_agents` calls on the already-member send path: pre-side-effect and pre-publication - no `list_relay_agents` call on that send path - relay failure and revocation remain fail-closed - internal owner-only mode hides other-owned agents - manual local Desktop testing by Wes: mention sends felt materially improved ## Notes This intentionally does not clear the composer early or add an optimistic timeline row. Publication still waits for fresh authorization. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz> |
||
|
|
ee992ff082 |
fix(desktop): restore release agent mentions (#6182)
## Summary - preserve OSS relay-agent mentions under shared channel and agent policy - restrict owner-only release builds to relay agents with cryptographically verified ownership matching the current user - remove the remote policy replay loop that repeatedly rebuilt the relay directory, while retaining focused polling and send-time revalidation - query relay profiles and managed policies by exact author coordinates to prevent noisy events from crowding out valid agents ## Diagnosis The packaged Block release compiles `BUZZ_DESKTOP_BUILD_AGENT_ACCESS_OWNER_ONLY`, while ordinary OSS/dev builds do not. Relay-discovered agents were filtered as if all remote agents were outside that owner-only boundary, so a same-owner agent running on another machine disappeared in the release even though the OSS path could look healthy. The fix uses the NIP-OA-authenticated owner from the relay directory as the cross-machine proof. Internal builds admit only verified same-owner agents and fail closed for missing, mismatched, stale/revoked, or unavailable ownership evidence. OSS builds retain shared channel/policy behavior. ## Validation - desktop focused unit coverage: 39 tests passed - desktop typecheck and focused static checks passed - focused Tauri Rust policy/directory tests passed - production-style E2E build succeeded - targeted Playwright mention scenarios passed: - owner-only release hides other-owned relay agent - owner-only release shows verified same-owner relay agent - OSS build shows shared `anyone` agent - repository pre-push hook passed on `4d40b6e5bb032f2c0755127172c50dee213f65a3`: - branch skew - desktop check and typecheck - desktop tests - Rust tests - Tauri checks - mobile tests --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
076081bfc6 |
Rename Bumble agent to Pollen (#5864)
## Summary - Rename the built-in Bumble agent to Pollen across desktop, onboarding, docs, and test fixtures. - Migrate existing stock definitions and instances in place while preserving customized fields and the stable persona coordinate. - Reserve the Pollen name by removing it from Fizz's generated-name pool. ## Validation - Pre-push desktop checks, typecheck, 4,791 frontend tests, Tauri clippy, and 2,432 native tests - Desktop E2E build --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
edc4a09aaa |
feat(workflows): add responsive library card actions (#6008)
**Category:** improvement **User Impact:** Users can scan what each workflow does and trigger, edit, duplicate, enable, disable, or delete it directly from the library. **Problem:** The workflow list buried common actions and did not expose each automation's trigger-to-action shape at a glance. **Solution:** Add a responsive workflow library with a persistent create tile, compact trigger/action diagrams, prominent workflow titles with supporting descriptions, and shared card actions while preserving existing detail, editor, and run-history entry points. Card toggles refresh both list and open-detail caches so status and definition stay consistent. <details> <summary>File changes</summary> **desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx** Adds a shared card menu for trigger, edit, duplicate, enable/disable, and delete actions. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Reworks cards around the prototype's visual hierarchy: color-coded trigger, action flow, sentence-case eyebrow, prominent title, supporting description, status, channel, and update date without a footer clock icon. **desktop/src/features/workflows/ui/WorkflowsView.tsx** Adds the responsive grid, create tile, mutation wiring, and list/detail cache invalidation. Container breakpoints keep cards two-across at medium widths and three-across in the 1280px desktop layout. **desktop/src/features/workflows/ui/workflowDefinition.ts** Adds immutable enabled-state updates plus narrow trigger and first-action readers used only to select card icons. **desktop/src/features/workflows/ui/workflowDefinition.test.mjs** Covers neutral icon selection, enabled-state immutability, and status presentation. **desktop/tests/e2e/workflows.spec.ts** Covers the create tile, title/description hierarchy, selected-card enable/disable consistency, and deterministic narrow/medium/wide captures while retaining existing action coverage. </details> ## Reproduction steps 1. Open **Workflows** and confirm the create tile stays first as cards flow from one to three columns with available width. 2. Confirm each card shows a sentence-case trigger eyebrow, prominent workflow title, supporting description when present, status, channel, and update date without a clock icon. 3. Open a card's overflow menu and trigger, edit, duplicate, enable/disable, or delete the workflow. 4. Leave the detail panel open while toggling and confirm its badge and JSON definition update with the card. ## Screenshots Real built E2E UI with representative workflow data at three viewport sizes. ### Narrow — 800 × 720  ### Medium — 1024 × 720  ### Wide — 1280 × 720  ### Card actions  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
122a8b8988 |
Projects v3: unify sharing, discussions, and issue ownership (#5792)
## Summary Projects v3 makes repository work shareable, discussion-aware, and easier to scan in one coherent workspace. People can copy canonical links, reopen the exact workspace tab, understand issue and pull-request context at a glance, find related channel conversations, and assign or unassign issues across Desktop and CLI. - **Unified workspace** — top-level sections sit above repository controls in one rounded workspace, with navigation positioned close to the page heading. README and Files retain branch selection; every section has a labeled icon header, and Issues and Pull Requests expose creation from a consistent right-aligned action. - **Repository management** — the repository selector is always available, including single-repository projects. Its integrated add flow lets project owners create a repository manually or select an existing repository without a separate toolbar button. - **Readable work-item lists** — issue and pull-request rows use plain-language context instead of opaque metadata. Files, commits, issues, pull requests, channels, and contributors share consistent row density and right-aligned timestamps, while deterministic fallback-avatar colors keep participants distinct on light backgrounds. Inbox pull-request metadata wraps between complete phrases and truncates long channel names instead of compressing copy into narrow columns. - **Reliable entity links** — projects, repositories, issues, pull requests, and commits have canonical `buzz://` links, preview cards, OS deep-link routing, and tab-aware navigation. Reopening the same link re-applies its destination instead of leaving the user on a locally selected tab. - **Related conversations** — repository and work-item views surface channels discussing the current entity, including participants, channel navigation, message context, and an explicit notice when discovery reaches its 500-result cap. - **Reversible issue ownership** — trusted assignment and unassignment events work across Desktop, Tauri, `buzz-sdk`, and `buzz issues`. Assignees appear in project views and the assigned inbox, while authorized users can remove assignments directly from the assignee row. Assignment state is derived chronologically from labeled Nostr notes. Issue authors and repository owners may change any assignee; other users may only assign or unassign themselves. Shared golden fixtures keep entity-link grammar and validation aligned across TypeScript and Rust. The branch also updates `webbrowser` to the patched release for RUSTSEC-2026-0257. ### Related issue N/A. ### Testing - [x] `just ci` — formatting, lint, typechecking, unit tests, and builds passed - [x] Full pre-push suite — organization, branch-skew, Desktop checks, typechecking, and tests passed on the latest push - [x] `cargo test -p buzz-cli` and focused `buzz-sdk` assignment tests passed - [x] Focused Tauri recipient-note and 500-result search-limit tests passed - [x] Desktop entity-link and issue-assignment unit tests passed - [x] Playwright smoke coverage passed for assignment, repeated entity-link navigation, repository create/select flows, section headers and actions, timestamp alignment, timeline icons, sentence-style issue/PR metadata, header spacing, avatar contrast, and Inbox metadata at stacked and side-rail breakpoints - [ ] Manual staging pass: link round-trips, Channels tab, assignment flows, and inbox routing ### Screenshots Pull requests explain who opened the request, where it lives, and which branch it comes from; fallback avatars remain visually distinct.  Issues use the same sentence-style hierarchy while keeping status and recency easy to scan.  The wide Inbox detail keeps author, timestamp, and origin context readable beside its metadata rail.  [View the complete six-state Projects v3 screenshot set](https://github.com/block/buzz/pull/5624#issuecomment-5268039672) and [the compact/wide Inbox comparison](https://github.com/block/buzz/pull/5624#issuecomment-5268614585). --- > Supersedes #5624, whose head commit accumulated permanently-queued required check suites (block-dco-check et al.) that GitHub never dispatched. History flattened into a single signed-off commit on latest main; tree verified byte-identical (`git merge-tree`) to merging the original branch into main. --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Co-authored-by: Wintermute <3f1797424fd9ad6653a83665c660517777cd7f8c228c0d5907f49e01537f3ca5@buzz.block.builderlab.xyz> |
||
|
|
f086eb6544 |
fix(link-previews): send while previews finish in background (#5697)
**Category:** fix **User Impact:** Messages send immediately after submission while link previews finish in the background, with an option to skip delayed preview preparation. **Problem:** Waiting for link-preview metadata or snapshot uploads kept the composer occupied after users pressed Send, while races between completion, timeout, and cancellation risked inconsistent payloads. **Solution:** Freeze and promote speculative preview work into a bounded background send task, clear the composer immediately, and publish exactly once with prepared previews or gracefully without them when skipped, failed, or timed out. https://github.com/user-attachments/assets/987d2f2c-679f-473a-965f-dfb279951e52 <details> <summary>File changes</summary> **desktop/src/features/communities/useCommunityInit.ts** Resets pending link-preview preparation when community context changes so work cannot cross community boundaries. **desktop/src/features/messages/lib/linkPreviewPreparationStore.ts** Adds the coordinator-owned preparation state machine, bounded fallback, Skip behavior, and exactly-once terminal publication handling. **desktop/src/features/messages/ui/ComposerUploadProgressOverlay.tsx** Extends floating background progress UI to include link-preview preparation. **desktop/src/features/messages/ui/ComposerUploadProgressPill.tsx** Adds the preparing-link-preview label and Skip action to the progress pill. **desktop/src/features/messages/ui/MessageComposer.tsx** Hands submitted preview work to the background coordinator and clears the composer immediately. **desktop/src/features/messages/ui/messageComposerAutoSubmit.test.mjs** Updates auto-submit unit coverage for coordinator-owned preview preparation. **desktop/src/features/messages/ui/messageComposerAutoSubmit.ts** Allows submit to promote unfinished preview work instead of blocking composer submission. **desktop/src/features/messages/ui/useComposerLinkPreviews.tsx** Starts preview work speculatively and exposes frozen preparation jobs for adoption by the send flow. **desktop/src/features/messages/ui/useMentionSendFlow.helpers.ts** Carries prepared preview tags through the mention and media payload helpers. **desktop/src/features/messages/ui/useMentionSendFlow.ts** Integrates prepared preview tags into final message publication. **desktop/src/shared/lib/useResolvedLinkPreviews.ts** Exposes the in-flight metadata promise so promoted work can be adopted rather than restarted. **desktop/tests/e2e/messaging.spec.ts** Covers immediate submit, upload handoff, Skip/completion races, failure fallback, auto-send, and exactly-once publication. </details> ## Reproduction steps 1. Enter a supported link and press Send while preview metadata or snapshot upload is still pending. 2. Confirm the composer clears immediately and the floating progress UI shows **Preparing link preview · Skip**. 3. Let preparation finish and confirm one message is published with its preview. 4. Repeat and choose **Skip**; confirm one message is published without waiting for the preview. 5. Simulate preview failure or timeout and confirm the message still publishes once without preview tags. ## Validation - TypeScript, Biome/format, file-size, px-text, and pubkey checks - Full desktop unit suite: 4,734 passed - Focused Playwright messaging suite: 5 passed - Push hooks at `86c0aa7de2ff81b79286c99bf23db12345adc6ca`: desktop check, typecheck, and tests passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
5acb930821 |
feat(desktop-messages): render compact Buzz permalink chips (#5638)
**Category:** improvement **User Impact:** Buzz channel, message, repository, pull request, and issue links now open reliably and display recognizable context in the desktop app. **Problem:** Buzz links could appear as raw or ambiguous URLs, and navigation links received during startup or community transitions could be dropped before the UI was ready. Repository and issue shares in particular required hover context to understand at a glance. **Solution:** Queue desktop channel/message navigation until the UI is ready, then render bare Buzz permalinks as icon-prefixed chips with concise entity context while preserving user-authored Markdown labels as ordinary links. <details> <summary>File changes</summary> **desktop/src-tauri/src/deep_link.rs** Adds validated channel-link parsing and a deduplicated, acknowledged queue so navigation survives frontend startup. **desktop/src-tauri/src/lib.rs** Registers the pending-navigation state and commands with the desktop application. **desktop/src/features/communities/useCommunityInit.ts** Resets queued navigation safely across community boundaries without leaking stale destinations. **desktop/src/features/messages/lib/channelLink.test.mjs** Covers valid, malformed, and canonical channel permalink forms. **desktop/src/features/messages/lib/channelLink.ts** Defines strict parsing and detection for `buzz://channel/<uuid>` links. **desktop/src/features/messages/lib/composerMessageLinkNode.test.mjs** Extends composer-node coverage for normalized Buzz link content. **desktop/src/features/messages/lib/composerMessageLinkNode.ts** Keeps composer link-node handling aligned with the expanded Buzz link surface. **desktop/src/features/messages/lib/remarkChannelDeepLinks.test.mjs** Verifies bare channel URLs become renderable deep-link nodes without touching code. **desktop/src/features/messages/lib/remarkChannelDeepLinks.ts** Transforms eligible bare channel links into dedicated Markdown nodes. **desktop/src/features/messages/lib/remarkEntityLinks.test.mjs** Covers bare repository, pull-request, and issue detection and code-span exclusions. **desktop/src/features/messages/lib/remarkEntityLinks.ts** Adds dedicated Markdown nodes for bare Buzz project entities. **desktop/src/shared/deep-link.test.mjs** Exercises queued navigation, acknowledgement, serialization, and community-switch behavior. **desktop/src/shared/deep-link.ts** Serializes pending deep-link drains and acknowledges destinations only after successful navigation. **desktop/src/shared/styles/globals/markdown.css** Aligns permalink icon geometry and spacing with agent mention chips. **desktop/src/shared/ui/markdown.test.mjs** Adds integration coverage for every permalink chip, authored labels, fallbacks, icons, and static rendering. **desktop/src/shared/ui/markdown.tsx** Routes channel and entity nodes through the shared presentation path while preserving authored link text. **desktop/src/shared/ui/markdown/BuzzLinkChip.tsx** Introduces the shared interactive/static permalink chip and authored-label inline-link components. **desktop/src/shared/ui/markdown/ChannelDeepLink.tsx** Renders channel shares and references with Hash icons, names, and shortened-ID fallbacks. **desktop/src/shared/ui/markdown/MessageLinkPill.tsx** Renders ordinary message shares with message icons and channel/message context while retaining sent-from-thread behavior. **desktop/src/shared/ui/markdown/entityLinks.tsx** Maps repositories, pull requests, and issues to Projects-aligned icons and contextual labels. **desktop/src/shared/ui/markdown/nodeCache.ts** Includes entity-link rendering in cached Markdown node handling. **desktop/src/shared/ui/markdown/utils.ts** Allows validated channel links through the Buzz URL transform. **desktop/src/shared/useMessageDeepLinks.ts** Drains queued navigation links safely and clears them during teardown. **desktop/src/testing/e2eBridge.ts** Extends the mock bridge with pending-navigation command behavior. **desktop/tests/e2e/community-rail.spec.ts** Verifies queued links do not cross community boundaries. **desktop/tests/e2e/navigation.spec.ts** Covers channel/message deep-link navigation during startup and active sessions. **desktop/tests/helpers/bridge.ts** Adds reusable deep-link mock state and acknowledgement helpers. </details> ## Reproduction steps 1. Run the desktop app and open a channel containing bare `buzz://channel`, `buzz://message`, `buzz://repo`, `buzz://pr`, and `buzz://issue` URLs. 2. Confirm each bare URL renders as one cohesive chip with a type icon, a useful name or shortened identifier, and no duplicated channel `#` character. 3. Add an authored Markdown link such as `[design discussion](buzz://issue?...)` and confirm the supplied label remains an ordinary link rather than becoming a chip. 4. Select channel and message links and confirm they navigate correctly in warm and cold-start states. ## Screenshots / demos Houston dark theme with custom purple accent (`#a855f7`), captured from rebased visual implementation `ad411cc06`; current head `0aafa144f` only adjusts E2E expectations for the visible mention-label behavior shown here. **Composer — channel, message, repository, pull request, and issue pills**  **Message list — channel, message, repository, pull request, and issue pills**  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
c8da06c5e9 |
Speed up initial direct messages (#5658)
## Summary - avoid blocking first-DM navigation on a full channel-list refresh - publish the initial message through the acknowledged HTTP path instead of waiting on a missing WebSocket acknowledgement ## Validation - 4,715 desktop unit tests - desktop typecheck and checks - focused new-DM Playwright coverage --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
bcf353c969 |
fix(desktop): enforce agent mention authorization at send boundaries (#5681)
## Summary - allow channel-member remote/headless agents only with current kind `10100` directory evidence, while stale member identities remain hidden - fail closed while managed/relay directories load, error, or background-refetch across channel, forum, and cached autocomplete surfaces - revalidate agent mention authorization immediately before normal sends and message-edit saves, including after deferred uploads - in owner-only builds, fetch fresh authoritative profile ownership at send time and deny missing, changed-owner, or unavailable proofs - preserve human mention tags when agent authorization is revoked or unknown Supersedes #5536 because its contributor-fork head cannot be updated by maintainers. ## Validation Exact head: `7278cdd5fbcee676c7b858ea098503c62eeeff0d` - mandatory pre-push suites passed: desktop check/typecheck/tests, Rust tests, mobile tests, desktop Tauri checks, branch-skew - desktop unit tests: 4,732 passed - focused edit/ownership regressions: 8 passed - focused mention E2E: 5 passed (remote positive, stale-member negative, directory error, pre-send revocation, mid-send revocation) - file-size ratchet passed One first focused E2E batch had a timing-only miss where the send click did not emit; the isolated rerun passed. One separate pre-push attempt hit the existing randomized passphrase separator test; the successful exact-head push reran and passed the mandatory suite. --------- Signed-off-by: JDiz00 <174381550+JDiz00@users.noreply.github.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: JDiz00 <174381550+JDiz00@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
2693e0db1f |
Make workflow run history authoritative in Desktop (#5780)
## Summary - persist stable workflow run `error_code` values separately from human diagnostics - expose NIP-98 authenticated, channel-authorized run history and approval reads with stable keyset pagination - connect Desktop to those authoritative reads and return the relay-created run ID on trigger - show truthful loading, failure, and pending-trace states, and do not render approval actions from non-actionable stored hashes ## Validation - pre-push `branch-skew`, `desktop-typecheck`, `desktop-test`, `rust-tests`, `desktop-tauri-checks`, and `desktop-check` all passed on `a097dbe5f` - Desktop tests: 4,761 passed, 0 failed - `cargo check -p buzz-relay` - `git diff --check` ## Remaining gate This does not claim a relay-backed Playwright workflow journey. The browser relay bridge still routes workflow invokes through in-memory handlers; that production-shaped acceptance gate remains follow-up work before Workflows can leave preview. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Co-authored-by: Mongo <5c25403eab7271f9f94ddd4f2b270e8cac2c92e2c830c51877cca6ec974ffb3f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <68157ebd23b3897c1991015c3038658ea916200c67d3a54620b0754d1b92f6e0@buzz.block.builderlab.xyz> |
||
|
|
a96af89526 |
Harden shared agent instruction review (#4220)
## Summary - render shared-agent instructions as literal text so Markdown cannot conceal spoiler contents, link destinations, or image sources - reject non-reviewable Unicode controls at every agent-definition boundary while preserving legitimate rendered emoji sequences - verify shared catalog event IDs and signatures before trusting authorship, coordinates, pagination, or executable content - preserve the exact system-prompt bytes between review and execution instead of silently stripping or normalizing content ## Security rationale Shared system prompts are executable configuration. Previously, catalog prompts were projected through the chat Markdown renderer, which could hide text, replace link destinations with benign labels, and turn image syntax into remote loads. Zero-width and bidirectional controls could also make reviewed text differ from what the agent executes. This change establishes a review invariant: the prompt a user sees is the prompt the agent executes. Definitions that cannot be reviewed faithfully are rejected rather than rewritten. Catalog events must also pass Nostr ID/signature verification before they can claim a publisher, coordinate, or cursor. ## What changed - catalog instructions render as exact literal text rather than rich Markdown - catalog relay events are verified on a fresh wire-shaped object before paging, coordinate selection, attribution, or projection - forged content, pubkeys, signatures, and invalid newer heads are ignored and cannot shadow a valid signed definition - TypeScript catalog parsing rejects unsafe remote definitions before they reach the UI - shared Rust validation covers persona create/update/import, inbound relay sync, definition-less managed-agent sync, and catalog publication paths - definition-less managed agents now fail closed on local create, local update, and publication before persistence or relay retention - linked managed agents validate their local name while treating the persona definition as authoritative; their inert record-level prompt is not executed or published - names reject layout controls; prompts retain ordinary newlines and tabs - legitimate emoji composition is supported, including contextual VS16, ZWJ, skin-tone, family, flag, and keycap sequences - detached selectors/joiners, bidirectional controls, tag characters, zero-width concealment, and other default-ignorables remain rejected - names are bounded to 128 characters and prompts to 64 KiB - contributor guidance documents the byte-for-byte review requirement for future sharing paths Validation reports the offending code point and never silently removes it. ## E2E recording [buzz-shared-agent-security-e2e.webm](https://github.com/user-attachments/assets/44d6b75f-0877-490f-bda4-a716fae3f700) The recording demonstrates: - a safe definition remains visible - a prompt containing zero-width `U+200B` is rejected - a name containing bidi override `U+202E` is rejected - the prompt is preserved exactly - spoiler, link, and image syntax remains literal and does not render or load ## Verification Passed locally: - `just test`: all 10 unit and Docker-backed integration stages - desktop frontend unit suite: 4,295 tests - persona catalog relay unit suite: 32 tests, including forged-event and cursor-shadowing cases - focused Rust definition-validation coverage: 3 local create/update tests and 6 publication-filtered tests - complete desktop Tauri library suite after rebase: 2,263 passed, 14 ignored, 0 failed - desktop Tauri clippy with warnings denied and Rust formatting - complete agent Playwright spec: 34 tests - the exact formerly failing `inbox-edit` immediate-attachment smoke test after rebase: 1 test - focused shared-agent publish, literal-review, hidden-control, signature, and cross-member import Playwright coverage - desktop E2E production build and TypeScript typecheck - changed-file formatting/lint and file-size ratchet - pre-commit secret scan and DCO signoff The branch was rebased onto current `main`, which includes the upstream attachment-button label fix. Fresh post-rebase GitHub CI is green for every required and selected check: Desktop Core, all four Desktop Smoke E2E shards, both Desktop E2E Integration shards and their aggregate, Desktop E2E Relay, Desktop Build (macOS), Windows Rust, Rust Lint, DCO, security scanners, and Desktop Release Candidate. The previously failing `Desktop Smoke E2E (3)` shard now passes. The repository-wide desktop check also reports existing CSS formatting/`!important` findings in `components.css` and `terminal.css`; neither file is changed by this PR. GitHub's Desktop Core lint and format stage passes on the rebased branch. --------- Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com> |
||
|
|
c86443c599 |
perf(desktop): persist channel snapshot hash (#5684)
## Summary - persist each relay/identity's complete channel list and server hash as one integrity-checked snapshot - paint the snapshot immediately on cold boot, then revalidate with `knownHash` - fail slow-never-wrong: malformed/legacy/partial snapshots and mismatched not-modified responses force an unhashed full fetch - add sidebar boot diagnostics and deterministic unit/E2E coverage for boot, identity/relay isolation, partial writes, mismatch fallback, and community switches ## Safety invariants - channel list and hash are serialized in one localStorage document and replaced together - snapshot ownership is scoped to normalized relay URL plus identity pubkey - a not-modified response is accepted only when its hash exactly matches the hash describing the available list - any missing or impossible hash/list pairing retries `getChannels(null)` before replacing persistence ## Validation At exact commit `19ca25d23c434cc0b8893a93691aaf4c77794f60` with a clean working tree: - `cd desktop && pnpm check && pnpm typecheck` — passed (existing informational Biome findings only) - `cd desktop && pnpm test` — 4,723 passed - `cd desktop && node --import ./test-loader.mjs --experimental-strip-types --test src/features/channels/channelSnapshot.test.mjs` — 13 passed - `cd desktop && pnpm exec playwright test e2e/sidebar-snapshot.spec.ts --grep "cold boot paints" --repeat-each=5` — 5 passed - `cd desktop && pnpm exec playwright test e2e/sidebar-snapshot.spec.ts` — 8 passed - push hooks repeated desktop check/typecheck and all 4,723 unit tests successfully The Playwright suite uses injected bridge delays. Its roughly 0.5–0.6 s snapshot paint and 3.0 s snapshot-to-live readings are synthetic invariant evidence, not production desktop performance measurements. ## Measurement context The controlled current-main investigation is documented separately in `RESEARCH/DESKTOP_PERF_DEEP_DIVE_2026_08_12.md`; its raw local artifacts are `.scratch/summer-perf-deepdive-results-v2.json`, `.scratch/summer-perf-deepdive-run.log`, `.scratch/summer-perf-deepdive-run-2.log`, and `.scratch/summer-perf-deepdive-build-2.log`. Those original timings are also synthetic Chromium/mock-bridge measurements and are not presented as shipped Tauri/WKWebView or production-relay numbers. ## Latest review delta At exact tip `e8e2b1d617aac7ea008258ad9974bbf8da9cd2eb`, storage-denial reads fail open to the live fetch, hashless retries reject `channels: null` before pair/persistence updates, identity-read failure enables a hashless live fetch, and repeated consumers reuse snapshot parsing/integrity validation by storage key + raw document. The four remaining review nits are deferred as non-blocking follow-ups. Validation at this tip: sidebar snapshot E2E 30/30 serial; desktop unit suite 4,725/4,725; full push gate green (desktop check/typecheck/unit, Rust, Tauri). --------- Signed-off-by: Summer <1fdd3cc104e2911eb3b2da6f97d1b25f4a7f3550ded4492b24ff1d95acd66766@buzz.block.builderlab.xyz> Co-authored-by: Summer <1fdd3cc104e2911eb3b2da6f97d1b25f4a7f3550ded4492b24ff1d95acd66766@buzz.block.builderlab.xyz> |
||
|
|
884ed8a5d3 |
fix(link-previews): proxy sent preview media (#5627)
## Overview **Category:** fix **User Impact:** Sent link previews now reliably display their thumbnail and favicon when the media is hosted on the relay. **Problem:** Sent preview cards loaded relay-hosted snapshot media directly, so authenticated relay requests could fail even though the snapshot itself was valid. **Solution:** Rewrite snapshot media at the shared card render boundary through Buzz's authenticated local media proxy, preserving the original display domain and rerendering when the proxy becomes ready. ## Changes <details> <summary>File changes</summary> **desktop/src/shared/ui/link-preview-attachment.tsx** Routes sent preview thumbnails and favicons through authenticated relay media handling above the Compact/Rich fork while preserving original metadata. **desktop/src/testing/e2eBridge.ts** Adds an opt-in proxy-readiness seam that deterministically re-arms the production media lookup when released. **desktop/tests/e2e/messaging.spec.ts** Covers the real send, snapshot, recipient, and card-render path for Compact and Rich previews, including fallback URLs, proxied URLs, and decoded image content. **desktop/tests/helpers/bridge.ts** Exposes the opt-in media-proxy startup state to E2E tests. </details> ## Reproduction Steps 1. Send a link whose preview snapshot includes a relay-hosted thumbnail and favicon. 2. Inspect the sent message card in Compact mode and confirm both images render after the local media proxy becomes ready. 3. Switch link previews to Rich mode and confirm the thumbnail and favicon continue to render. 4. Run the focused Playwright regression: `pnpm exec playwright test tests/e2e/messaging.spec.ts --project=smoke --grep "sent link preview media uses the authenticated proxy"` ## Before / After | Before | After | | --- | --- | | Relay-hosted preview media fails to load. | The sent preview thumbnail and favicon render through the authenticated media proxy. | |  |  | Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
63f961c7e4 |
Refine channel settings and profile panels (#5574)
## Summary - simplify channel settings into concise detail, member, canvas, and action sections - align human and agent profiles around shared rows, segmented tabs, and top-level actions - add agent runtime presentation, sticky glass behavior, and scroll-linked action transitions ## Snapshots ### Channel settings  ### Agent info  ### Agent runtime  ## Validation - `pnpm -C desktop check` - `pnpm -C desktop test` (4,604 passed) - `pnpm -C desktop build:e2e` - focused channel settings and agent profile Playwright tests --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
f35930104b |
fix(desktop): remove 0.5.9+ perf regressions, speed up get_channels (#5599)
Desktop input latency regressed sharply for users on v0.5.9 and worsened on latest main: multi-second stalls when clicking back into the app, slow fresh boots, intermittent lockups, and scroll/mouse degradation. Reverting to `119a84897` (pre-0.5.9) was confirmed to resolve it, isolating the regression to that range. Profiling a live production renderer plus a commit-level audit of the range found three independent, additive causes — fixed here — plus a long-standing `get_channels` cost that made every remaining refetch expensive, also addressed here. ## 1. Focus-return refetch storm (`refetchOnWindowFocus`) #5490 wired TanStack's `focusManager` to app focus and flipped ~20 query sites to `refetchOnWindowFocus: true`. A focus return after >60s away fires them all within milliseconds — and a click into an unfocused window *is* a focus return, so the burst runs before the click is processed. That is the "click into the composer, wait 5 seconds" symptom, and it also explains why mouse input feels worse than keyboard (clicks arrive with focus transitions; typing happens while already focused). A 5-second `sample` of a live production renderer caught a single window activity-state transition consuming ~1.25s of main-thread time, dominated by `JSON.parse` in the focus listener's microtask drain. #5535 already established the fix pattern but applied it to only two families (channels, home-feed). This PR extends the same 5-minute `staleTime` discipline to the remaining families: pulse (×5), workflows (×4), agents (×4), forum (×2), presence, user-status, custom-emoji, channel-templates, and the persona catalog. Polling cadences and push-invalidation paths are untouched — interval refetches and `invalidateQueries` both bypass `staleTime`, so live-update behavior is unchanged. Each gated family exports its focus-refetch policy as an options object that the production hook spreads into `useQuery`, and a `focusRefetchPolicy.test.mjs` drives a `QueryObserver` with that same production object — locking the policy behaviorally (fresh focus return → 0 fetches; stale → refetch) and failing if a hook's `staleTime`/`refetchOnWindowFocus` wiring drifts. Four families deliberately keep tighter freshness, all surfaces where the 5-minute gate would suppress the only refresh path and none of which feed the app-wide storm: `repo-sync-status` keeps its fresh focus refetch (its inline comment documents the "committed in a terminal, switched back to the app" flow as intended); the workflow-runs list stale-gates at 10s because a remotely-started run has no push invalidation and its conditional 1s poll is off while the cache shows no active runs; the workflow list queries (`useChannelWorkflowsQuery` and the all-channels aggregate) stale-gate at 10s because they have no poll and no relay subscription, and mutation-driven invalidation only covers this renderer — remote workflow creates/edits/deletes surface only via focus refetch; and the managed-agent log stale-gates at one poll tick (30s) so returning to a live agent log refreshes immediately. Run approvals keep the 5-minute gate under `RUN_APPROVALS_FOCUS_STALE_TIME_MS` — their focused 10s poll already covers freshness. ## 2. Synchronous localStorage sweep on the boot/focus path #5453's stale-cache sweep synchronously `getItem` + `JSON.parse`s every whitelisted localStorage entry on the main thread (multi-MB on seasoned profiles), scheduled with a `requestIdleCallback` timeout of 1.5s that guaranteed it landed mid-boot, and re-armed on every hidden→visible transition — stacking it onto the exact moment the focus storm fires. #5454's `trimSelfProfileCaches()` additionally scanned every localStorage key on every `writeSelfProfileCache()` call (which fires per relay self-profile delivery at boot). Now: the first sweep waits `BOOT_SWEEP_FLOOR_MS` (30s) after startup, the scan is time-sliced across idle callbacks, and the visibility trigger is removed — boot-delayed plus hourly still covers the 14-day TTL contract. The sliced sweep re-checks staleness immediately before each removal (a key rewritten fresh mid-sweep survives), isolates per-key storage errors so one bad entry can't strand the rest of the snapshot, defers oversized values once rather than parsing them on a zero-budget slice, guarantees forward progress on timeout-fired callbacks, and cancels its scheduled slice when stopped. The profile trim keeps a lazily-initialized memoized key count so the common under-cap write is O(1); the full parse scan runs only when the count exceeds a cap, resyncs if external deletions made it stale, and a failed scan skips the trim instead of aborting the write. Sweep semantics (rules, TTLs, eviction) are unchanged, and tests cover the scheduling, slice-progress, error-isolation, defer-once, and trim short-circuit behaviors. ## 3. The macOS window was never opaque #5478's glass appearance is correctly opt-in at the CSS layer, but the compositor cost was baked in deeper than its native `on_webview_ready` transparency call: the main window is declared `"transparent": true` in `tauri.conf.json` (added for the original glass work in #1671), which makes tao call `NSWindow.setOpaque(false)` at creation and resolve every later `set_background_color(None)` to `clearColor` — and no runtime `setOpaque(true)` path exists through tauri, while wry's runtime background setter can only force the WKWebView's `drawsBackground` off, never back on. So "restore the platform default" was unreachable: every launch, glass or not, ran with a non-opaque NSWindow, defeating WindowServer's opaque-window compositing fast path and forcing full window compositing every frame — compounded by the existing `backdrop-blur` chrome overlapping the scrolling timeline. This matches the compositor-shaped symptoms (scroll and pointer input degrading first). The window is now created opaque (`"transparent": false`) and the NSWindow layer is never made transparent at runtime. Glass never needed a transparent window: behind-window `NSVisualEffectView` vibrancy renders inside opaque windows (this is how Finder and Notes draw vibrant sidebars); it only requires a transparent WKWebView canvas, which the `set_window_vibrancy` enable path already establishes at runtime (`macos-private-api` compiles that in independent of the window flag). Enabling glass installs the vibrancy layer and then makes only the webview canvas see-through; disabling clears the vibrancy layer — the canvas may stay non-drawing afterwards (wry's flag is one-way at runtime), which is harmless because glass-off CSS paints fully opaque above an always-opaque NSWindow. The boot-path first-frame backing writes touch only the NSWindow backing color and are therefore inert to glass state regardless of how they order against the `ThemeProvider`'s vibrancy call on a persisted-glass-on cold boot. Glass-off users (the default) get an end-to-end opaque window from boot for the first time. ## 4. `get_channels`: serial round-trips and a multi-MB payload on every refetch The stale gates in (1) cut refetch frequency; this cuts the cost of the refetches that legitimately remain (boot, and focus returns after more than 5 minutes away — previously still a multi-second stall). `get_channels` made ~8 fully serial relay round-trips (~3.2–3.6s at 1,100+ channels), then shipped the full `ChannelInfo` list — including every channel's member pubkeys — across IPC, where the renderer's `JSON.parse` of the multi-MB payload froze the main thread (the ~1.25s stall captured in the live sample). - **Concurrent stages**: the membership chain, the open-channel directory scan, and the hidden-DM snapshot run concurrently, as do the member-count and last-message queries that follow. The critical path drops from ~8 sequential round-trips to 2 phases. Filters, limits, pagination, and merge semantics are unchanged. - **Not-modified short-circuit**: the command now takes a client-supplied content hash (FNV-1a 64 over the channel list, canonicalized by id and excluding `last_message_at`) and omits the channel list from the response when nothing else changed. Last-message timestamps — which change on nearly every message anywhere — ship as a small separate map that the client overlays onto its cached list with reference preservation, so React Query's structural sharing also skips downstream re-renders. On a typical refocus the renderer parses kilobytes instead of megabytes. The hash is stored in the query cache itself, tying its lifecycle to the data it describes so a community switch can never leak a stale hash. The E2E mock bridge speaks the new payload shape — including the complete `last_messages` map the client treats as authoritative — and hash canonicalization plus overlay reference-preservation are unit-tested on both sides. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
cd2aa5c12d |
Add glass appearance and cohesive settings (#5478)
## Summary - add an opt-in native glass sidebar with opacity controls and live theme previews - refine sidebar spacing and Buzz-only active rows while preserving production defaults - unify settings section cards, subtitles, and agent runtime rows ## Validation - repository format, lint, type, and file-size checks - 4,538 desktop tests and 2,270 native desktop tests - desktop and web production builds - 1,261 mobile tests in the completed full gate - focused Playwright appearance, sidebar, settings, pairing, and runtime coverage --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> |
||
|
|
b0795a10ea |
Add Send to channel for thread messages (#5305)
## Summary - Share eligible self-authored or owned-agent thread messages into the parent channel as new top-level messages. - Link the shared message back to the exact root thread with a semantic channel label and excerpt. - Add a dedicated channel-arrow icon plus ownership and navigation coverage. ## Validation - Desktop lint, size, and text guards - Desktop TypeScript build and all 4,543 unit tests - Focused Playwright send-to-channel and thread-link navigation tests --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
be48ce98bd |
fix(link-preview): reliably render previews sent right after they resolve (#5245)
## Overview **Category:** fix **User impact:** Link previews no longer disappear when a message is sent while preview metadata or media is still settling. Fast Enter, rapid Enter, and confirmed-draft auto-send now preserve the preview without duplicate sends or stale tags. **Problem:** The composer could look ready before its sender-authored snapshot tag existed. Send paths could then race preview resolution/upload, while debounced preview state could attach a tag for a URL that had already been removed. The same timing also caused confirmed-draft auto-send to be consumed without sending. **Solution:** - Debounce preview resolution to avoid card flicker while typing, then disable every submit path while a supported external preview settles. A 2-second escape cap still permits a bare-link send if resolution stalls. - Keep submit synchronous: acquire a composer-local lock before asynchronous send work, read ready tags from the live URL set, and reject Enter/form submits while a snapshot is pending. - Retry confirmed-draft auto-submit until preview settling clears, then submit exactly once. - Upload thumbnail and favicon independently. A failed upload shows a toast and degrades to the surviving media (or text-only) rather than leaving the card spinning. - Exclude message-edit mode from preview resolution, upload, and Save gating. Edit-time preview snapshots remain follow-up #5273. - Canonicalize fragment-bearing URLs for preview lookup/snapshot identity while preserving the original fragment links in message text. ## Link preview state walkthrough Captured using PR #5245's actual public Open Graph metadata and artwork. The deterministic E2E bridge controls only upload timing so the transient disabled state can be captured reliably. | State | Expected behavior | Screenshot | | --- | --- | --- | | **1. Snapshot upload pending** | The real PR preview is visible, but Submit remains disabled until its sendable snapshot tag is ready. Click and Enter cannot send a bare link during the settling window. |  | | **2. Snapshot ready** | Once snapshot upload settles and the tag is ready, the same preview remains and Submit becomes active. |  | | **3. Message sent** | The sent event carries the snapshot tag and renders the PR title, description, and artwork inline instead of degrading to a bare URL. |  | ## Regression coverage - Enter during metadata resolution or snapshot upload cannot send early. - Paste-and-immediate-Enter sends after settling; rapid Enter submits exactly once. - Confirmed-draft auto-send waits for settling and fires exactly once. - Removed/replaced URLs cannot leak stale snapshot tags or media refs. - Thumbnail upload failure toasts and sends with the surviving favicon. - Edit mode does not resolve/upload previews or gate Save. - Fragment variants share a canonical preview while original fragment links remain clickable. - Existing ready-preview, suppression, bare-link fallback, and multi-preview behavior remains covered. ## Reproduction steps 1. Open a channel and paste a supported external URL into the composer. 2. Press Enter immediately, before preview metadata/media finishes settling. 3. Before this fix, the event could be sent without its preview snapshot (or confirmed-draft auto-send could be lost). With this fix, submit waits behind the disabled state and fires once with the matching snapshot tag. 4. Remove or replace the URL and press Enter inside the debounce window. The sent event contains tags only for URLs still present in the submitted content. ## Validation All required PR checks are green, including Desktop Core, Desktop Smoke E2E shards, Desktop E2E Integration shards, macOS build, security checks, and DCO. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
3f2f32641f |
Polish desktop onboarding flow (#5310)
## Summary - standardize onboarding navigation and horizontal step transitions - refine the avatar editor with live preview, segmented modes, search, skin tones, and reduced-motion-safe feedback - simplify harness/default-model actions and supporting copy ## Testing - desktop typecheck and static guards - desktop E2E build - 9 focused onboarding smoke tests - 4 focused onboarding/profile integration walkthroughs - 4,535 desktop unit tests --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> |
||
|
|
1922d49cb2 |
feat(desktop): adding rich link previews to messages (#3818)
## Overview **Category:** improvement **User impact:** Link previews appear in the composer and travel as privacy-safe sender-authored snapshots, so recipients never contact the linked site merely by opening a conversation. **Problem:** Cold-cache link paste could freeze the composer before the URL painted; recipient-side unfurling leaked visits; invalid or unresolved preview work could interfere with sending or leave dead cards behind. **Solution:** Paint pasted links before starting cold resolver work, resolve only in the sender's composer, attach only complete validated snapshots at Send, and render authored snapshots without recipient fallback fetching. ## Behavior - **Cold paste stays responsive:** bare and angle-bracket URL paste paths commit the visible link before resolver work begins. - **Sender-only fetching:** metadata is resolved while composing; recipients render only the sender-authored snapshot. - **Send never waits:** pending, failed, invalid, and unsendable previews are omitted. They do not block or cancel the message. - **Terminal misses disappear:** failed, timed-out, or 404 resolver results remove the composer card while preserving visible link text. - **Display-text links work:** Markdown links such as `[review the pull request](…)` produce and send the same snapshots as bare URLs. - **Compact and Rich presentation:** Compact remains the default; Rich preserves source description line breaks and paragraphs. - **Immediate draft-wide dismissal:** clicking × immediately hides all previews for the draft, suppresses links pasted later, and emits only `["link-preview", "none"]`. No confirmation detour. Suppression resets after send or clearing the draft. - **Zero recipient fallback:** missing, stale, malformed, off-relay, unsupported, or suppressed snapshots remain ordinary visible links; recipients never regenerate them. ## Implementation - Resolve previews from deferred composer URL state so paste can paint first. - Upload finished preview media to the active community relay and snapshot only valid, sendable media references. - Atomically capture ready snapshots at submit time; never append a late preview after send. - Validate snapshot and suppression tags in desktop/native and relay ingestion, rejecting duplicate or mixed forms. - Render composer previews as stable 55px attachment cards at desktop and narrow widths. - Add deterministic E2E coverage for cold paste, ready/pending/failed/invalid previews, display-text links, multiline Rich descriptions, immediate dismissal, later-pasted links, and suppression reset. ## Validation Validated head: `9807ba8952f190e76153834abf8ab61dd40be5e2` - Push hooks passed: `check-push-org`, branch skew, desktop check, mobile tests, desktop tests, Rust tests, and desktop Tauri checks. - Focused screenshot E2E at the validated head: 5/5 passed across Compact/Rich composer and recipient states, 800px/420px geometry, display-text links, multiline descriptions, and immediate dismissal. - PR CI was triggered for this exact head and is currently running; completed checks are green at the time of this update. - Worktree is clean and both PR head and validated branch resolve to `9807ba895…`. ## Screenshots ### Compact composer | Loading | Ready | |---|---| |  |  | ### Rich composer | Loading | Ready | |---|---| |  |  | ### Responsive composer | 800px loading | 800px ready | |---|---| |  |  | | 420px loading | 420px ready | |---|---| |  |  | ### Recipient presentation | Compact | Rich | |---|---| |  |  | ### Display-text Markdown link | Composer | Recipient | |---|---| |  |  | ### Rich multiline description  ### Immediate dismissal | Before × | Immediately after × | |---|---| |  |  | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
6eb65919f1 |
feat(identity): recover desktop identity from a signed-in phone (#4845)
**Category:** new-feature **User Impact:** People who lose a desktop identity can securely restore it from a signed-in Buzz phone without creating a replacement identity. **Problem:** A fresh or identity-lost desktop could not recover its existing full Buzz identity from an already-authorized phone. **Solution:** Add a SAS-confirmed reverse NIP-AB transfer, durable desktop import, a dedicated mobile recovery entry point, and clearer desktop recovery dialogs with tested loading, drag-and-drop, and failure states. https://github.com/user-attachments/assets/e9215c9c-80d0-462f-9161-0fa184ca2f74 <details> <summary>File changes</summary> **crates/buzz-core/src/pairing/session.rs** Adds the reverse encrypted payload and source-completion state transitions used for phone-to-desktop recovery. **desktop/src-tauri/src/commands/identity.rs** Exposes the existing guarded identity commit path for recovery imports. **desktop/src-tauri/src/commands/pairing.rs** Adds recovery-mode pairing, durable nsec import, start serialization, stale-task protection, and explicit rejection of unsupported recovery payloads. **desktop/src-tauri/src/lib.rs** Registers the recovery pairing command. **desktop/src/app/App.tsx** Refreshes the recovered identity before continuing onboarding. **desktop/src/features/onboarding/machineOnboarding.ts** Adds recovery transitions to the onboarding state machine. **desktop/src/features/onboarding/ui/BackupPasswordTimeline.tsx** Adds the visual backup-to-password-to-unlock progression. **desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx** Implements QR generation, copy fallback, SAS confirmation, cancellation, expiry, and completion UI. **desktop/src/features/onboarding/ui/MachineOnboardingFlow.tsx** Connects private-key, phone, and backup recovery paths to the onboarding flow. **desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx** Polishes recovery dialogs, backup drag-and-drop, loading stability, and security copy. **desktop/src/shared/api/tauri.ts** Keeps the existing pairing API surface focused on standard desktop-to-mobile pairing. **desktop/src/shared/api/tauriPairing.ts** Adds the recovery pairing invoke without growing the ratcheted shared API file. **desktop/src/testing/e2eBridge.ts** Mocks recovery pairing commands and lifecycle events for browser tests. **desktop/tests/e2e/identity-lost.spec.ts** Covers lost-identity entry, QR/copy recovery, SAS, cancellation, expiry, success, errors, backup import, drag-and-drop, and screenshots. **desktop/tests/e2e/onboarding.spec.ts** Verifies recovered identities continue through harness setup without replacement-key side effects. **mobile/lib/features/pairing/pairing_page.dart** Adds recovery-only scanning and explicit identity-handoff warnings. **mobile/lib/features/pairing/pairing_provider.dart** Recognizes recovery codes, returns the signed-in nsec after mutual SAS approval, and waits for desktop completion. **mobile/lib/features/settings/settings_page.dart** Accepts the recovery route builder at the app composition boundary to preserve feature isolation. **mobile/lib/features/settings/settings_page/connection_section.dart** Adds the signed-in “Send identity to desktop” settings action. **mobile/test/features/pairing/pairing_page_test.dart** Covers recovery-only validation and handoff messaging. **mobile/test/features/pairing/pairing_provider_test.dart** Covers reverse payload encryption, confirmation ordering, success, failure, timeout, and cleanup. </details> ## Reproduction steps 1. Launch Buzz Desktop with identity-lost state and choose **Recover from your phone**. 2. Confirm the QR and persistent **Copy pairing code** fallback appear without layout shift. 3. On a signed-in phone, open **Settings → Send identity to desktop**, scan or paste the recovery code, and compare the six-digit SAS on both devices. 4. Confirm on both sides and verify Desktop restores the identity and continues to harness setup. 5. Repeat from identity-lost state with **Recover from a backup file**; verify picker and drag-and-drop both advance to password entry and restore the encrypted backup. 6. Exercise cancellation, mismatched/unsupported codes, expired sessions, and an invalid backup; verify each returns actionable, non-stuck UI. ## Screenshots ### Desktop phone recovery — complete flow | Recovery entry | Pairing QR | Code match | Receiving identity | |---|---|---|---| |  |  |  |  | ### iOS Simulator — complete handoff flow | Settings entry | Recovery scanner | Manual recovery code | Code confirmation | |---|---|---|---| |  |  |  |  | ### Encrypted backup recovery — adjusted file flow | File picker | Drag-and-drop target | Password step | |---|---|---| |  |  |  | ## Verification - `cargo test -p buzz-core pairing` — 71 passed - `just mobile-test` — 1,169 passed - `pnpm build:e2e && pnpm exec playwright test identity-lost.spec.ts --project=smoke` — 15 passed - Full pre-push gates — desktop checks, desktop unit tests, Rust tests, Tauri checks, and mobile tests passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
19b41e9c8e |
fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990)
## Problem Users on v0.5.5 report "Can't reach the relay" toggling with brief "connected" flashes (field reports; also the macOS confirmation in #4908). #4737 closed the stuck-reconnect gaps; this is the opposite failure: the client redials fine, but then kills its own healthy socket. Mechanism (all on `main`): 1. AUTH succeeds → session emits `connected` (`relayClientSession.ts:583`), then awaits `replayLiveSubscriptions()`. 2. Paged channel backfill issues history REQs (`relayReconnectReplay.ts`, page limit 500). 3. A `CLOSED rate-limited:` on a **history** REQ arms the rate-limit gate but still rejects the history promise (`relayClosedRecovery.ts:38-51`). 4. The rejection escapes `replayLiveSubscriptions()` → `resetConnection()` tears down the authenticated socket. 5. Reconnect → AUTH OK → replay rate-limited again → loop. Each iteration re-spends the rate-limit budget, so the loop is self-sustaining. ## Fix Contain backfill failures inside the replay. Each subscription's paged backfill now retries behind the rate-limit gate up to `PAGE_REPLAY_MAX_ATTEMPTS` (3), then degrades to live-only **for this connection**. Socket health no longer depends on backfill success. Nothing is lost: the replay cursor (`lastSeenCreatedAt`) only advances on delivered events, so the next reconnect replays the same missed window. ## Red/green proof - Commit 1 (Pinky): e2e injecting `CLOSED rate-limited:` into the mid-replay history REQ — **red on main** (expected 1 reconnect dial, observed 2; connected-flash then teardown). - Commit 2 (this fix): same test **green unchanged** — one dial, state stays `connected` through the rate-limit hint plus the next backoff window. Why existing coverage missed it: the prior rate-limit e2e pre-armed the gate *before* replay (replay politely waits), and the CLOSED-injection test targeted a *live* subscription (which has its own retry path). Nobody injected back-pressure from the history REQ itself. ## Verification - `pnpm test`: 4374/4374 pass. - `playwright test tests/e2e/relay-reconnect.spec.ts`: 14/14 pass, including the new spec. - `tsc --noEmit` clean; Biome clean on touched files (pre-existing warnings on main in `personaCatalogRelay.test.mjs` / `terminal.css` untouched). ## Not addressed here (follow-ups from the same field reports) - AUTH terminal latch is too aggressive for relay-internal `error:` rejections (3 strikes during a relay bad window → stuck until click/relaunch; #4908). - Server-side: `relay.drainJitterMs` (#4542) defaults to 0 — enabling it on the hosted relay removes the deploy thundering herd that triggers these rate-limit storms. --------- Signed-off-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz> Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> |
||
|
|
16cc3de6d6 |
fix(desktop): enforce owner-only access in internal builds (#4053)
## Problem Managed agents in internal Buzz builds should answer only their owner. Previously, an agent could keep a broader access setting and respond to other people, which did not match the access policy for internal builds. This PR makes owner-only access effective for every managed agent in internal builds and makes that restriction clear in the Desktop UI. Open source builds remain configurable. ## Changes - Enforce owner-only access when any managed agent starts or is deployed from an internal build. - Show the agent access control as locked to **Only me** in Desktop, with an explanation of why it cannot be changed. - Keep Welcome teammates working under the same rule without triggering unnecessary restarts. - Leave open source build behavior unchanged. This changes effective runtime access without rewriting stored or relay-advertised settings. The companion [#4064](https://github.com/block/buzz/pull/4064) explains the restriction in-thread when someone without access mentions an agent. The enforcement will remain inactive in shipped builds until [squareup/buzz-releases#74](https://github.com/squareup/buzz-releases/pull/74) marks internal releases during the build. ## Screenshots | Before | After | | --- | --- | |  |  | ## Tests Added coverage for: - Runtime enforcement for [locally run agents]( |