mirror of
https://github.com/block/buzz.git
synced 2026-11-01 06:51:17 +00:00
bd2ea9a5fedd3404947b892619eeeb7dfff661c1
2192 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bd2ea9a5fe |
test(readState): complete witness inventory — production-path Amendment C, hook-level pre-ready, storage_failed, deferred-refusal
Final witness completions for Thufir pass-2 verification:
1. Amendment C witnesses now use real fetchAndMerge() load path:
- drain_crashAfterAppliedUnread_newerRemoteRead_noRebump: delivers the
remote C=6 event via an encrypted relay event; fetchAndMerge() decrypts
and merges it — no direct overrideRegisters writes, no hand-set isLoadComplete.
- drain_crashAfterAppliedRead_newerRemoteUnread_noRebump: mirror, delivers
remote S=7 via relay event through production path.
2. New manager-level witnesses:
- drain_deferredRefusalAfterRestart_intentHydratedAndRefused (Test 43):
intent persisted to v2 blob, hydrated on restart, refused on drain
— confirms deferred-refusal surfaces outcome.
- markChannelUnread_storageFailed_intentInMemoryOnly (Test 44):
storage failure on enqueue: intent stays in-memory, no v2 blob write
— confirms graceful degradation under storage_failed.
3. Hook-level (useUnreadChannels) pre-ready witnesses:
- preReady_markChannelUnread_appearsInUnreadChannelIds_beforeLoadComplete:
pending unread is visible via computePreReadyUnread before load completes.
- preReady_markChannelRead_suppressesForcedEntry_sourcePreserved_refusalRestoresMultiSource:
queued read suppresses committed forced entry without deleting sources.
4. noopMarkChannelUnread/noopMarkChannelRead in useReadState.ts enqueue
intents when no manager is available, so pre-ready presentation works even
before the relay client is initialized.
5. useUnreadChannels.ts: condense else branch to single line (ratchet fix,
1019 wc -l = 1020 split, within the 1021 limit).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
24e87e85c7 |
fix(readState): v4 plan conformance — pending-intents consolidation, single-flight retry controller, biting witnesses
Closes all 7 IMPORTANT + 2 MINOR findings from Thufir pass-1. Core changes: - Consolidate pending-intents storage into override-state.v2 blob (Paul's design ruling). Single write point for registers, receipts, and intents eliminates the double-commit window; no migration needed since pending-intents.v1 never shipped. - Implement retryLoad() single-flight controller: bounded exponential backoff (1s->2s->4s...60s cap), reconnect coalescing into pendingRetryOnComplete, destroy/identity cancellation, retryAttempt reset on complete verdict. - sourceScope + readTarget captured in enqueue path so drain performs exact source cleanup at the correct frontier position. - refused-unread rollback: snapshot prior forced-unread entry before ordered write; restore exact snapshot on refusal; delete only when no prior existed. - Surfaced drain outcomes via onDrainOutcome hook; genuine refusals (uint32_overflow, budget_exhausted, storage_failed) trigger toast; already_inactive is silent success; load_incomplete never surfaces. - Cleanup atomicity: intent + receipt deleted in a single persistLocalState() call (one v2 blob write) so they are never observable separately. - createDrainContext typed factory replaces this-as-unknown-as-DrainContext cast. - NIP-RS.md: added normative refusal-reason vocabulary list. Biting witnesses added (readStateManager.test.mjs, +12 tests): - Amendment A: gen2 enqueued mid-replay, gen1 register rolled back - Both inversion directions (unread->read, read->unread in-flight) - compare-delete preserves newer intent - Amendment C via production fetchAndMerge load path (not direct register write) - Atomic intent+receipt deletion witness - Controller: automatic backoff, reconnect during loadInFlight, stale-gen ordering - Controller: destroy-with-timer, exactly-one-drain-per-generation - Store/UI: identity-swap-during-drain, deferred refusal surfaces outcome Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ba097710b0 |
fix(readState): route pre-ready reads through tri-state API, open rawUnread gate
Fixes three Thufir pass-1 conformance defects in the incomplete-load path: 1. applyOverrideRead() no longer returns early before calling markChannelRead() when load is incomplete. The manager's queued branch is now reachable from the UI pre-ready. load_incomplete refusal (no relay client) still fails closed as overrideStillActive. Pre-ready null liveness is no longer treated as known absence; the null short-circuit is guarded behind isReadStateReady. 2. Both markContextRead() call sites (single-channel and markAllChannelsRead) are gated on overrideApis.isReadStateReady — no frontier mutation fires while the load is incomplete. Drain performs the authoritative advance on the next complete-load transition. 3. rawUnread opens the pre-ready gate for local forced entries and pending intents via computePreReadyUnread() (extracted to readStateOverride.ts). Precedence: pending read suppresses > pending unread asserts > committed forced entry. Partial manager/observed state is never consulted pre-ready. Replaces two obsolete fail-closed tests with v4-conformant witnesses. Adds four required biting witnesses (a)-(b)-(c)-(d): preReady_read_witness_a_queued_intent_recorded preReady_read_witness_b_zeroMutationZeroPublish preReady_unread_witness_c_pendingUnread_visible_during_loading preReady_read_witness_d_queued_read_preserves_sources_overrideStillActive Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
dec35a502f |
chore(readState): remove dead clearChannelUnreadSource from useForcedUnreadActions
useForcedUnreadActions.clearChannelUnreadSource was the pre-#3966 local- only source-removal path. It was superseded by useClearChannelUnreadSource in readStateOverride.ts, which routes last-source removals through applyOverrideRead so the NIP-RS register receives its C-bump cross-device. useUnreadChannels.ts already destructures only markChannelUnread from useForcedUnreadActions; the clearChannelUnreadSource return value has been dead since the branch introduced useClearChannelUnreadSource. Remove the dead callback and the return slot (plan standing MINOR). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
d61d8b1178 |
refactor(readState): extract drain helpers into readStateDrain.ts
Split the 427-line-over-ratchet readStateManager.ts by moving the
drain-path logic (drainPendingIntents, markChannelUnreadDirect,
markChannelReadDirect, markChannelUnread, markChannelRead) into a new
readStateDrain.ts module. The manager methods become thin delegations
that cast `this` to the DrainContext interface.
readStateDrain.ts exports a DrainContext interface (the public subset of
ReadStateManager state and methods needed for drain operations) and five
standalone functions that implement the Amendment A/B/C drain semantics
verbatim — no behavior change.
File-size ratchet (merge-base
|
||
|
|
d1900aa58a |
chore: merge origin/main (2ea938501) into duncan/nip-rs-unread-format
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> * origin/main: fix(reactions): support max-length custom emoji (#3833) feat(desktop): allow leaving your final community (#3621) fix(buzz-agent): recover from context-window 400s instead of sticking (#4946) docs(persona-pack): fix stale desktop import instructions (#4500) fix(desktop): route macos notification clicks (#4799) feat(mobile): sync themes per community (#3767) feat(desktop): sync themes per community (#3653) feat(desktop): cap OpenClaw agent parallelism at 5 (#4019) fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805) Fix mobile message timeline bounce (#4862) Polish mobile bottom sheets and profile cards (#4911) Fix media attachment actions (#4849) fix(desktop): remove join API token control (#4897) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
d2514c7636 |
fix(readState): route last-source clear through override mark-read seam
When clearChannelUnreadSource removes the last source from a channel's forced-unread entry, the user's intent is an explicit mark-read of that override. Before this fix the clear was purely local: the forced entry was deleted but the NIP-RS register never received a C-bump, so liveness stayed ACTIVE and the channel remained bold forever in the sidebar. Fix: add useClearChannelUnreadSource in readStateOverride.ts that calls applyOverrideRead on last-source removal. On overrideCleared the entry is deleted; on overrideStillActive (refused C-bump) the source entry is kept — the same fail-closed semantics as the explicit markChannelRead path. Non-last-source removals are unchanged (purely local, register stays active). Remote-only registers are unaffected (no local entry → early return). Both source paths covered: useHomeInboxReadState clearChannelUnreadSource call on last Inbox source removal, and useChannelUnreadState clearChannelUnreadSource call when forcedUnreadMsgRef empties (manual path). Fixes: channel-activity-popover.spec.ts E2E regression where Inbox 'Mark as read' cleared the inbox source but left the NIP-RS register active, keeping the channel bold forever after all Inbox threads were marked read. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
2ea9385015 |
fix(reactions): support max-length custom emoji (#3833)
**Category:** fix **User Impact:** Custom emoji with valid 64-character names can now be used as reactions without errors. **Problem:** Buzz accepted 64-character custom emoji names during registration, but rejected them as reactions after the required surrounding colons made the payload 66 characters. Validation also differed between desktop, SDK, relay, and storage boundaries. <img width="554" height="47" alt="image" src="https://github.com/user-attachments/assets/4013452f-210e-4dd3-9003-f45ff3b28dc8" /> **Solution:** Keep the product limit at 64 ASCII characters for custom emoji names, enforce it consistently when emoji sets are registered, and allow only valid matching custom reaction payloads up to 66 characters. Widen the reaction projection to preserve the wrapped payload while retaining the existing 64-character limit for ordinary reactions. <details> <summary>File changes</summary> **crates/buzz-sdk/src/builders.rs** Defines the shared custom emoji boundaries and covers accepted 64-character and rejected 65-character shortcodes. **crates/buzz-relay/src/handlers/ingest.rs** Validates emoji-set shortcodes and permits 66-character reactions only when they are valid colon-wrapped custom emoji with a matching tag. **crates/buzz-db/src/event.rs** Adds storage regression coverage for maximum-length custom emoji reactions. **crates/buzz-db/src/migration.rs** Verifies the reaction column migration is applied correctly. **desktop/src/shared/api/customEmoji.ts** Enforces the existing 64-character shortcode maximum during desktop normalization and registration/import. **desktop/src/shared/api/customEmoji.test.mjs** Covers the desktop shortcode boundary. **migrations/0027_long_reaction_payloads.sql** Widens stored reaction payloads to 66 characters for the two required surrounding colons. **schema/schema.sql** Keeps the desired schema aligned with the migration. </details> ## Reproduction Steps 1. Register or import a custom emoji whose ASCII shortcode is exactly 64 characters. 2. Select that emoji as a reaction to a message. 3. Confirm the reaction publishes, persists, and renders without an error. 4. Attempt to register a 65-character shortcode and confirm it is rejected. 5. Publish an ordinary or malformed reaction over 64 characters and confirm the relay rejects it. ## Verification - `cargo test -p buzz-sdk`: 243 passed - `cargo test -p buzz-db`: 94 passed, 152 Postgres-required tests ignored - `pnpm test` in `desktop`: 3,859 passed - `cargo test -p buzz-relay`: 795 passed, 9 existing Postgres-unavailable failures, 35 ignored; new reaction boundary tests pass directly - `cargo fmt --all -- --check` - `git diff --check` Originating Buzz channel: `f2ec9671-d78e-4cde-894c-9f4c458c7f1f` --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> |
||
|
|
719f9730d4 |
feat(desktop): allow leaving your final community (#3621)
**Category:** improvement **User Impact:** People can leave their final Buzz community and return to **Join or create a community** without losing their signed-in identity. **Problem:** Buzz Desktop blocked people from leaving when only one community remained. Its existing remove action also changed local configuration without ending relay membership. **Solution:** Allow the final community to be left. Buzz now asks the relay to end membership, removes the community locally only after acceptance, and returns the person to the community selector while keeping their identity signed in. If other communities remain, Buzz switches to one of them. Relay rejection or timeout keeps the community in place and shows an actionable retry error. <details> <summary>File changes</summary> **desktop/src/features/communities/leaveCommunity.ts** Adds signed kind 28936 publishing for active and inactive community relays with actionable timeout handling. **desktop/src/features/communities/leaveCommunity.test.mjs** Covers event shape, relay selection, acceptance gating, rejection, timeout messaging, and cleanup. **desktop/src/features/communities/useCommunities.tsx** Allows final-community removal and clears community-specific storage without touching identity. **desktop/src/features/communities/resolveCommunityRemoval.test.mjs** Covers final, active, and inactive community removal state transitions. **desktop/src/app/useCommunityNavigationTransitions.ts** Gates local removal on relay acceptance and routes to a fallback community or setup selector. **desktop/src/app/AppShell.tsx** Passes the asynchronous leave operation through shell entry points. **desktop/src/features/communities/ui/EditCommunityDialog.tsx** Replaces the local-only remove action with a pending-aware Leave Community action that retains actionable errors. **desktop/src/features/communities/ui/CommunitySwitcher.tsx** Enables leaving the final community and carries the asynchronous callback. **desktop/src/features/sidebar/ui/AppSidebar.tsx** Carries the asynchronous leave callback through sidebar props. **desktop/src/features/sidebar/ui/CommunityRail.tsx** Enables leaving the final community from rail settings. **desktop/src/features/sidebar/ui/SidebarProfileCard.tsx** Carries the asynchronous leave callback through profile community settings. **desktop/src/testing/e2eBridge.ts** Teaches the mock relay to accept NIP-43 leave events. **desktop/tests/e2e/community-rail.spec.ts** Updates leave interactions and verifies final-community setup navigation, storage cleanup, and identity preservation. </details> ### Reproduction steps 1. Run Buzz Desktop with a signed-in identity and one joined community. 2. Open Community settings and choose **Leave Community**. 3. Confirm the app shows **Join or create a community** and the existing identity remains signed in. 4. Repeat with two communities and confirm leaving the active one switches cleanly to the remaining community. 5. Reject or withhold the relay `OK` response and confirm the community remains configured with an actionable error in the dialog. ### Test plan - `pnpm check` - `pnpm build` - `pnpm test` (3,913 passing) - `pnpm build:e2e && pnpm exec playwright test tests/e2e/community-rail.spec.ts --grep "final community"` <img width="557" height="316" alt="image" src="https://github.com/user-attachments/assets/b628182f-cba5-451d-ae4b-bee8d8dd19aa" /> --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: npub14ndfusear8wdpe4kss8h7juc7wjk78atnqzf63zvppcpneknv4sq6x9370 <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: npub14ndfusear8wdpe4kss8h7juc7wjk78atnqzf63zvppcpneknv4sq6x9370 <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
ed4b3e7afa |
fix(buzz-agent): recover from context-window 400s instead of sticking (#4946)
Provider `context_length_exceeded` 400s permanently wedged agent sessions: the turn errored, the oversized history persisted in the in-memory session, and the usage baseline stayed frozen at the last successful sub-threshold reading (failed requests report no usage), so the preflight handoff gate never fired again — every later prompt failed identically until an agent restart. The byte-truncation fallback never intervened because it is a request-body limiter (`estimated_bytes`), not a context-window defence; at context-window scale it is a measured no-op. This adds the reactive recovery path: - **Typed classification.** `AgentError::LlmContextExceeded` is classified at both non-success provider terminals — the shared `post()` (Anthropic, OpenAI, Databricks) and `openrouter_post()` — on `status == 400` plus a context-window body match, so ordinary 400s stay terminal. - **Forced handoff.** A context-400 forces a summarize-handoff that bypasses `should_handoff()` and `BUZZ_AGENT_MAX_HANDOFFS`, bounded by its own per-turn budget (`MAX_CONTEXT_RECOVERIES_PER_RUN = 3`). - **Shrink ladder.** The summarize prompt budget halves from the observed rejected history size — not from `max_context_tokens`, the number the provider just contradicted — rung to rung, with a 4096-byte floor. A summarize call rejected for the same reason takes the next rung instead of re-sticking. At the floor (overflow dominated by unshrinkable frame: system prompt, tool schemas, live prompt) recovery is refused and the provider error surfaces clearly instead of self-healing. - **Baseline reset.** The stale usage baseline is cleared when a request fails, so the preflight gate cannot stay frozen sub-threshold on retries. Named behavior changes: 1. **Anthropic and OpenRouter errors now carry the `(model)` stamp.** Provider arms return their `Result` into the central error mapper instead of early-returning past it, making the code match its documented single-convergence contract at that mapper. 2. **`max_rounds` now counts completions the loop acts on.** A request rejected with a context-400 that is then successfully recovered refunds its round before the retry, paired 1:1 with a consumed recovery rung, so the round cap is neither weakened nor able to drop a recovered turn unanswered. Related: #4805 — the complementary proactive fix (per-session handoff-cap kill switch that let sessions grow to the provider wall). #4805 prevents reaching the wall; this PR recovers at it. --------- Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
ccdaa16161 |
docs(persona-pack): fix stale desktop import instructions (#4500)
## Summary The desktop app no longer imports persona packs the way the docs described. `PERSONA_PACK_SPEC.md` and the `meadow-core` example still pointed users at a `.zip` import through "My Teams / My Agents → Import" and a future "Install Pack" button — none of that exists anymore. The app only imports agent/team **snapshots** (`.agent.json`/`.agent.png`, `.team.json`/`.team.png`), and a persona-pack `.zip` is explicitly rejected. ## Fix Updated both docs to describe the current import paths (Agents / Agent teams sections, snapshot files only) and added a note that persona packs and desktop snapshots are separate, non-interchangeable formats today. Fixes #4468 --------- Signed-off-by: SomSamantray <92726151+SomSamantray@users.noreply.github.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> |
||
|
|
7334ad1e16 |
fix(desktop): route macos notification clicks (#4799)
## Summary - deliver macOS notifications through `UNUserNotificationCenter` - route notification clicks to the referenced channel or thread through the existing frontend activation path - preserve click targets across cold startup and frontend remounts with a small process-wide activation queue - keep Linux notification activation behavior unchanged ## Architecture A single `UNUserNotificationCenterDelegate` is installed during Tauri setup. Each notification stores its navigation target in `userInfo`. On click, Rust queues the target before emitting a wake-up event; the frontend atomically drains the queue and dispatches the existing notification action. The queue is the source of truth, which prevents cold-start loss and duplicate delivery. ## Validation Verified at `a81241611d617becf7640bee6fe56b5cdb4d0fab`: - Biome format/check and lint - TypeScript typecheck - repository and desktop-Tauri `cargo fmt --check` - repository and desktop-Tauri Clippy with `-D warnings` - full pre-push desktop tests and Tauri workspace checks/tests - desktop production build Manual macOS validation passed: after explicitly ad-hoc signing the local bundle with `xyz.block.buzz.app`, the operator confirmed real Notification Center delivery and click navigation. <details> <summary>Local macOS test procedure</summary> Tauri's generated ad-hoc signing identifier is not accepted by `UNUserNotificationCenter`. Re-sign the local bundle with its bundle identifier and keep other Buzz copies closed: ```bash just desktop-release-build APP="$HOME/.cache/cargo-target/aarch64-apple-darwin/release/bundle/macos/Buzz.app" codesign --force --deep --sign - \ --identifier xyz.block.buzz.app \ --entitlements desktop/src-tauri/Entitlements.plist \ "$APP" codesign --verify --deep --strict --verbose=2 "$APP" pkill -x buzz-desktop || true open -n "$APP" ``` </details> Buzz channel: `55e2bfca-1b38-48fb-9dc2-584d400501f3` --------- Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
05150c1188 |
feat(mobile): sync themes per community (#3767)
**Category:** new-feature **User Impact:** Mobile now keeps each community’s appearance in sync with desktop, including theme, accent, and system-mode preference. **Problem:** Appearance choices were device-local, so the same account could look different between desktop and mobile. Live sync could also stop after the relay closed a subscription. **Solution:** Store each community’s encrypted appearance preference on its relay using the shared desktop wire contract, restore it from a local identity-scoped cache, and apply replacement events live. Closed subscriptions now recover with guarded backoff and fetch the latest preference so no update is lost during the gap. <details> <summary>File changes</summary> **mobile/lib/app.dart** Connects community appearance state to the authenticated app lifecycle. **mobile/lib/features/settings/accent_picker_page.dart** Aligns mobile accent choices and selection behavior with the shared catalog. **mobile/lib/features/settings/settings_page/appearance_section.dart** Clarifies the active appearance and hides accent controls when the Buzz theme owns its neutral accent. **mobile/lib/features/settings/theme_picker_page.dart** Persists catalog theme choices through the community-scoped provider. **mobile/lib/shared/theme/accent_colors.dart** Matches desktop’s accent catalog and wire values. **mobile/lib/shared/theme/buzz_theme.dart** Keeps Buzz visually neutral without discarding the user’s stored accent for other themes. **mobile/lib/shared/theme/community_theme_preference.dart** Defines and validates the versioned desktop-compatible appearance payload. **mobile/lib/shared/theme/community_theme_provider.dart** Coordinates cache-first appearance loading with account and community changes. **mobile/lib/shared/theme/community_theme_sync.dart** Adds encrypted NIP-78 relay persistence, live replacement handling, deterministic ordering, safe seeding, and resilient subscription recovery. **mobile/lib/shared/theme/theme.dart** Exports the community appearance modules. **mobile/test/features/settings/theme_picker_page_test.dart** Covers the updated settings behavior. **mobile/test/shared/crypto/nip44_interop_test.dart** Proves Dart decrypts a desktop-produced nostr-rs NIP-44 v2 preference. **mobile/test/shared/theme/buzz_theme_test.dart** Covers Buzz’s neutral rendering and stored-accent restoration. **mobile/test/shared/theme/community_theme_preference_test.dart** Covers wire parsing, validation, migration, and future-version handling. **mobile/test/shared/theme/community_theme_sync_test.dart** Covers cache/relay lifecycle, replacement ordering, switching races, absence-only seeding, and closed-subscription recovery. </details> ## Reproduction steps 1. Sign into desktop and mobile with the same account and join the same community relay. 2. On desktop, choose a distinctive non-Buzz theme and accent; mobile should update without a local toggle. 3. Restart mobile and confirm it restores the same appearance. 4. Change the mobile theme and accent and confirm desktop follows. 5. Leave mobile idle or backgrounded through a relay reconnect, then change desktop again; mobile should resubscribe and catch up automatically. 6. Switch communities and confirm each community restores only its own appearance. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> |
||
|
|
43cced308d |
feat(desktop): sync themes per community (#3653)
**Category:** new-feature **User Impact:** Users can keep a distinct Appearance scheme for each community and restore it on another desktop signed in with the same identity. **Problem:** A single global theme makes it harder to distinguish among communities, and local-only preferences do not follow a user to another device. **Solution:** Save each community's stable theme, accent, and system-following selection as private encrypted relay state, backed by a responsive local cache and guarded against switch races, invalid future records, and relay failures. <details> <summary>File changes</summary> **desktop/src/app/App.tsx** Mounts the community-scoped theme controller inside the active community lifecycle. **desktop/src/features/settings/lib/appearanceScopeCopy.test.mjs** Covers active-community and fallback labels used to explain Appearance scope. **desktop/src/features/settings/lib/appearanceScopeCopy.ts** Builds a safe, trimmed label for the currently active community. **desktop/src/features/settings/ui/SettingsPanels.tsx** Clarifies which Appearance controls are per-community and which apply globally when multiple communities exist. **desktop/src/shared/constants/kinds.ts** Defines the NIP-78 application-data event kind used for theme preferences. **desktop/src/shared/theme/CommunityThemeController.tsx** Coordinates cached appearance, encrypted relay retrieval, live updates, reconnect behavior, and safe community switching. **desktop/src/shared/theme/ThemeProvider.tsx** Exposes a single appearance application path so synchronized preferences use the existing renderer and persistence behavior. **desktop/src/shared/theme/communityThemePreference.test.mjs** Covers contract validation, user/relay isolation, malformed records, cache failures, and switch-race decisions. **desktop/src/shared/theme/communityThemePreference.ts** Defines the versioned stable preference contract, safe defaults, local cache keys, and persistence guards. **desktop/src/shared/theme/communityThemeSync.test.mjs** Covers relay absence, unreadable records, unavailability, seeding safety, and teardown of pending writes. **desktop/src/shared/theme/communityThemeSync.ts** Encrypts theme preferences to the user, publishes and retrieves NIP-78 state, and handles ordering and lifecycle safety. </details> ### Reproduction steps 1. Join at least two communities and open **Settings → Appearance**. 2. Choose a different theme, accent, or system-following mode in each community. 3. Switch between the communities and verify each one restores its own scheme without overwriting the other. 4. Sign in on another desktop with the same Nostr identity, join the same community, and verify its saved scheme is restored from that community's relay. 5. Disconnect the relay, change Appearance, and verify the UI remains responsive and the local fallback is retained. ### Screenshots / demos <img width="1733" height="948" alt="image" src="https://github.com/user-attachments/assets/5afeabaa-0def-482c-9b87-8a880ee0a467" /> <img width="700" height="412" alt="Screen Recording 2026-07-29 at 4 39 52 PM" src="https://github.com/user-attachments/assets/d58da329-aec5-4324-a4b2-cbcb2702a81a" /> --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> |
||
|
|
6c40ce394f |
feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
OpenClaw connects to a single shared Gateway daemon. Spawning the
default 10 ACP workers per agent is both resource-expensive and
architecturally wrong — each worker opens a separate gateway connection.
Tyler's ruling: cap at 5, lower if needed.
## Contract
Store the requested value (1–32) verbatim at every persistence and wire
boundary. Apply `effective = min(requested, harness_cap)` only at the
four enforcement points:
| Boundary | Implementation |
|---|---|
| Local spawn | `BUZZ_ACP_AGENTS` env var in child `Command` |
| Remote deploy | `launch.policy_env["BUZZ_ACP_AGENTS"]` + legacy
`parallelism` field |
| Restart badge | `SpawnConfigSnapshot.parallelism` stores effective
value; the diff surface displays what actually runs |
| UI copy | Amber hint when requested > cap; no `max` attribute, no
save-path clamp |
`BUZZ_ACP_AGENTS` is added to `RESERVED_ENV_KEYS` — the Desktop resolves
the effective value into `policy_env`; a user-supplied override in `env`
would bypass the cap and is silently stripped.
## Changes
**`managed_agents/parallelism.rs`** (new) — policy core:
- `OPENCLAW_MAX_PARALLELISM = 5`
- `harness_max_parallelism(command)` — keyed on
`normalize_command_identity` so path prefixes, `.exe` suffixes, and
other cosmetic differences are ignored
- `effective_parallelism(command, value)` — identity for uncapped
harnesses
- `acp_agents_value(command, parallelism)` — `env("BUZZ_ACP_AGENTS", …)`
helper
**`runtime.rs`** — spawn clamp: `BUZZ_ACP_AGENTS =
acp_agents_value(effective_command, record.parallelism)`
**`agents_deploy.rs`** — deploy egress clamp: `build_deploy_payload`
resolves `effective_parallelism` once from `descriptor.command`; both
`launch.policy_env["BUZZ_ACP_AGENTS"]` and the legacy top-level
`parallelism` field use that value — the two are always consistent
regardless of stale `record.agent_command` pins
**`spawn_snapshot.rs`** — `from_inputs` stores
`effective_parallelism(&descriptor.command, record.parallelism)` in the
`parallelism` field. Over-cap edits that don't change the pool (e.g. 10
→ 8, both clamp to 5 on OpenClaw) produce equal snapshots; cap crossings
(8 → 3) produce different snapshots.
**`AcpRuntimeCatalogEntry.max_parallelism: Option<u32>`** — derived from
the static definition command, not the probed `entry.command` (which may
be `null` for unavailable entries), so unavailable OpenClaw entries
still carry the cap. Propagated through all four catalog constructors
(builtin discovery, preset catalog construction, custom discovery,
custom-save response), IPC types
(`RawAcpRuntimeCatalogEntry.max_parallelism`), and the frontend catalog
type.
**UI** — `EditAgentAdvancedFields` and `PersonaAdvancedFields` show an
amber hint when `selectedRuntime.maxParallelism` is set and the current
value exceeds it. Cap and label come from the catalog entry — no
hardcoded 5 in TS. No `max` attribute on inputs; the input stays
`type="text"` with 1–32 copy.
**Docs** — `docs/remote-agents.md`: `BUZZ_ACP_AGENTS` moved from the
deliberately-non-reserved section to reserved; new contract documented.
`desktop/src/features/agents/AGENTS.md`: command-keyed execution policy
documented as the sanctioned second metadata source feeding the catalog
projection.
## Tests
**Rust** (`parallelism.rs`):
- `policy_table` — `harness_max_parallelism` and `effective_parallelism`
across all openclaw variants and uncapped harnesses
- `acp_agents_value_openclaw_above_cap_is_capped` — spawn-env seam
- `override_direction_*` — both override directions (openclaw runtime +
goose override; goose runtime + openclaw override)
- `summary_persona_inherited_*` — live persona wins over stale
`agent_command`
- `snapshot_export_carries_requested_definition_parallelism` — requested
value travels wire/sync unchanged
**Rust** (`spawn_snapshot/tests.rs`):
- `openclaw_above_cap_parallelism_snapshots_equal` — stored 10 vs 8,
both clamp to 5 → snapshots equal
- `openclaw_cap_crossing_parallelism_snapshots_differ` — 8 (clamps to 5)
vs 3 → snapshots differ
**Rust** (`discovery/presets.rs`):
- `openclaw_preset_unavailable_carries_max_parallelism` /
`openclaw_preset_available_carries_max_parallelism` — catalog metadata
present with `command: null` and with a resolved path
**Rust** (`agents_deploy.rs`):
- `launch_block_openclaw_over_cap_policy_env_is_capped` — direct
`launch.policy_env` seam
-
`deploy_payload_json_stale_goose_record_live_openclaw_descriptor_both_capped`
— stale `record.agent_command=goose`, live descriptor=openclaw: both
fields cap to 5
-
`deploy_payload_json_stale_openclaw_record_live_goose_descriptor_both_uncapped`
— stale `record.agent_command=openclaw`, live descriptor=goose: both
fields pass through requested
- `deploy_payload_json_explicit_openclaw_override_both_capped` —
explicit `agent_command_override=openclaw`: both fields cap to 5
**Rust** (`persona_events/stale_pin_tests.rs`):
- `apply_persona_snapshot_goose_to_custom_harness_drops_stale_goose_pin`
— custom-direction stale-pin drop (builtin pin → loaded custom harness
via `update_loaded_harness_registry`)
**TypeScript** (`agentParallelism.test.mjs`):
- `parallelismCapHint` — at/below cap (null), above cap (hint includes
label and cap value), singular form for cap=1, uncapped harness (null)
**TypeScript** (`tauri.test.mjs`):
- `fromRawAcpRuntimeCatalogEntry` round-trips `max_parallelism` →
`maxParallelism`; absent when `undefined`
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
6df7eba24d |
fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
`BUZZ_AGENT_MAX_HANDOFFS` compared against the session-cumulative `handoff_count` (persisted across prompts). After N handoffs a long-lived session hit the cap permanently: `maybe_handoff()` returned `Skipped` on every subsequent prompt, the 16 MiB byte-truncation fallback never bound before a 1M-token provider wall, and the session wedged on the first 400 with no recovery path. Thufir's session log shows 8 days of cap-forced truncation before the first `context_length_exceeded` 400. The fix replaces the session-level cap comparison with a local `handoff_attempts` counter constructed at the start of `run()` and passed into `maybe_handoff()`. The counter resets on every `session/prompt` turn so `BUZZ_AGENT_MAX_HANDOFFS` caps compaction loops within a single turn while allowing unbounded compactions across a session's lifetime. The session-cumulative `handoff_count` is retained for log context only and is not reset. Steer-driven rounds share the per-turn budget automatically since steers inject into the running `run()` loop, not a new call. - Move `handoff_attempts` increment to before `summarize()` so failed, empty, and cancelled summarize calls each consume one budget slot — the cap cannot be bypassed by a repeatedly-failing summarizer - Upgrade cap-forced `Skipped` from `INFO` to `WARN`; add structured fields for `session_id`, attempt count, projected tokens, and threshold so the cap→wall pairing is attributable per session - Document `max_handoffs` in `config.rs` as a per-`session/prompt`-turn bound - Three new behavioral regression tests: per-turn reset proven across two separate turns; within-turn cap proven via multi-round tool-call turn; failed summarize proven to burn the attempt budget Note: this is the proactive half of the context-window fix. The reactive `context_length_exceeded` 400 recovery path is owned by Sami's branch (`buzz-ctxfix-sami`, Tyler's crew); this PR is intended to land after that one. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>mobile-v0.8.0-rc.3 |
||
|
|
0c6842931b |
Fix mobile message timeline bounce (#4862)
## Summary Stop repeated follow-latest scrolling after layout changes in channels and DMs. ## Validation - `flutter analyze lib/features/channels/channel_detail_page.dart` - `flutter test test/features/channels/channel_detail_page_test.dart` - Full mobile pre-push suite --------- Signed-off-by: kenny lopez <klopez4212@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> |
||
|
|
27b51144f3 |
Polish mobile bottom sheets and profile cards (#4911)
## Summary - standardize mobile sheets with shared spacing, close controls, action tiles, haptics, and motion - add uniform native concentric corners on iOS 26+ while preserving the Android sheet shape - refresh profile actions/status and normalize membership and huddle timeline spacing ## Validation - `just mobile-check` - `just mobile-test` (1,165 tests) - signed iPhone Release build and device install - Android debug build and Pixel 10 install ## Snapshots ### Channel actions  ### Profile card  --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
f2ce575b62 |
Fix media attachment actions (#4849)
## Summary - Upload photos immediately while keeping videos queued for background upload. - Move image annotation and video spoiler actions to thumbnail hover overlays. - Preserve the image editor's existing Draw and Spoiler controls. ### Snapshots #### Image annotation overlay  #### Image editor controls  ## Testing - `pnpm typecheck` - `pnpm check` - Focused attachment, drawing, and spoiler smoke tests - Pre-push desktop tests (4,286 passing) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Honey <47c18026466e670a1618fd7de0ef32b9ff75d6e0b5ccf255d13c8c3d674ed115@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
b67546c337 |
refactor(readState): extract file-size ratchet overages and update forcedUnreadStore header
Extract helpers to bring three over-limit files within the repository file-size ratchet: - AppShell.tsx (1001→999 wc): extract inline error ternary to errorMessage() in AppShell.helpers.ts. - useUnreadChannels.ts (1091→1018 wc): extract UseUnreadChannelsOptions, CATCH_UP_LIMIT, channelCatchUpEventKinds, the four root-id stores, and the pure resolve helpers to useUnreadChannelsHelpers.ts. Update import paths in nip-rs-unread-ui.test.mjs and unreadReadMarker.test.mjs. - relayClientSession.ts (1103→1085 wc): extract subscribeToChannelLive and subscribeToTypingIndicators inline filter objects to buildChannelLiveFilter() and buildTypingIndicatorFilter() in relayChannelFilters.ts. Remove now-unused KIND_CHANNEL_THREAD_SUMMARY and CHANNEL_EVENT_KINDS from relayClientSession kinds import. Also update the forcedUnreadStore.ts module header: the old "NOT synced to the relay" claim is false after this PR's NIP-RS override layer is in place; replace with an accurate description of the localStorage/ relay relationship (doc-only, no behavior change). All moves are mechanical — zero semantic change in any moved region. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
2034e693a8 |
fix(desktop): remove join API token control (#4897)
## Summary Remove the nonfunctional API-token option from the existing-community join flow. ## Validation - Focused Playwright join-flow coverage - Add-community screenshot coverage Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
1eb6a0f2e4 |
chore: merge origin/main (014562c06) into duncan/nip-rs-unread-format
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
014562c063 |
fix(desktop): allow shared agent mentions (#4913)
## Summary - admit relay-discovered agents to autocomplete when their response policy authorizes the viewer - require authorization in the exact active stream/forum channel for mentions, while keeping community-wide discovery for member invitation - fail closed for relay-only agents in DMs and unresolved composer contexts - re-authorize cached autocomplete rows after policy/channel changes so stale agent suggestions cannot leak back in - preserve managed-agent behavior and explicitly reject stale agent-marked channel members absent from both live directories ## Validation - `pnpm --dir desktop test` — 4,288 passed - `pnpm --dir desktop typecheck` - `pnpm --dir desktop check` - `pnpm --dir desktop build:e2e` - focused Playwright mention matrix — 12 passed - focused Playwright member-invitation matrix — 2 passed - pre-push hooks after rebase to current `origin/main` — desktop check and 4,288 tests passed - independent correctness/privacy re-review cleared with no remaining blocker ## Related competing PRs This supersedes or overlaps #2333, #3056, #4242, #4137, #2314, #4058, and #2605. This version adds exact-channel authorization, fail-closed DM/context handling, cached-row reauthorization, forum coverage, outbound mention-tag coverage, explicit stale-member coverage, and add-member discovery coverage. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
ff0b7982f1 |
Polish mobile top navigation (#4778)
## Summary - Polish mobile Home, Activity, Search, and Settings navigation chrome. - Add progressive Buzz gradients/frost, aligned theme colors, dividers, typography, and section spacing. - Refine Search and Settings motion, including automatic keyboard focus on search activation. <img width="630" height="1368" alt="C78FA3CE-F2B3-45F2-B9F5-7EA7500778CC" src="https://github.com/user-attachments/assets/5935514b-d894-4010-80dd-a938363fee93" /> <img width="630" height="1368" alt="5C058A73-1879-476A-881C-531ACC256D84" src="https://github.com/user-attachments/assets/5266c841-17ee-49e8-9841-b06d84f4195f" /> <img width="630" height="1368" alt="3F50ADB7-9BDA-4A8D-A81E-20560C3B9EA6" src="https://github.com/user-attachments/assets/f615f61e-9e96-4bce-b261-ae5ec54db872" /> <img width="630" height="1368" alt="35ECE741-01F3-4B79-80C5-1DDD447121A7" src="https://github.com/user-attachments/assets/b5145186-9884-44eb-8ebc-f3303831c0a4" /> ## Validation - `flutter analyze` - Focused Home, Activity, Channels, Search, theme, and footer widget tests - Full pre-push checks, including mobile tests, desktop checks, and Tauri checks - On-device iPhone review during the visual polish pass --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: npub1glqcqfjxdens59scl477pmejh8lht4hqkhx0y4w38jxr6e6w6y2sm29y4e <47c18026466e670a1618fd7de0ef32b9ff75d6e0b5ccf255d13c8c3d674ed115@buzz.block.builderlab.xyz> Signed-off-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: npub1glqcqfjxdens59scl477pmejh8lht4hqkhx0y4w38jxr6e6w6y2sm29y4e <47c18026466e670a1618fd7de0ef32b9ff75d6e0b5ccf255d13c8c3d674ed115@buzz.block.builderlab.xyz> Co-authored-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz> |
||
|
|
4674750b7e |
fix(release): tag immutable desktop candidates (#4811)
## Summary Redesign the permanent Desktop release flow so unrelated merges to `main` cannot invalidate an already reviewed, green release candidate. - Tag the immutable, API-confirmed release PR head instead of its later squash commit. - Treat the merged PR—including an authorized owner/admin bypass—as publication authorization, while requiring trusted check evidence that was complete at merge time. - Make tag creation idempotent and collision-safe: an existing tag succeeds only at the exact candidate SHA, and create races refetch before accepting equality. - Replace ancestry-based previous-release discovery with a validated metadata ledger for side-history candidate tags. - Compute the next release from the prior frozen base to the new frozen base, excluding only the prior release squash SHA so unrelated commits remain in the changelog. - Preserve schema-1 production-tag migration and reject malformed metadata or equal/decreasing versions. - Update operator documentation for the normal squash-merge workflow. This is the reusable release process for `0.5.6` onward, not the retired one-shot `0.5.5` recovery path. ### Invariants covered - Candidate creation → unrelated `main` merge → authorized squash merge → immutable candidate tag. - Trusted producer IDs and merge-time completion timestamps; DCO's bounded post-merge exception remains isolated. - Missing/spoofed checks, tampered candidates, ambiguous PR associations, conflicting tags, and equal/decreasing versions fail closed. - Same-SHA retries succeed; different-SHA collisions fail. - Legacy schema-1 tag-on-main migration and schema-2 side-history accounting both preserve the correct next-release changelog. ### Related issue N/A — follows the Desktop release failures in #4788 and #4800 and the recovery revert in #4808. ### Testing At clean commit `6a91fbed8147a48cf174997de0c3e4cb2fb26474`: - `scripts/test-desktop-release-candidate.sh` - `scripts/test-release-ref-contract.sh` Both focused suites passed with HEAD unchanged. Princess Donut cleared the security/provenance surface, including the hostile merge-time timestamp cases. Mongo cleared the side-history ledger, migration, version-order, documentation, and contract-test surface. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
efe1893dd3 |
fix(channels): restrict private-channel invitations (#4612)
This change requires an active owner or administrator for third-party additions to private channels. The relay validator and transactional database authority enforce the same rule, including removed-member reactivation and role-change paths. Idempotent self-target behavior remains available, while ordinary members can no longer extend private-channel access to another identity. ## Testing - `git diff --check origin/main...codex/security-private-channel-invite-authority` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> |
||
|
|
ad538bfb1e |
fix(acp): reject unattended permission requests (#4609)
This change removes the ACP permission-bypass mode, defaults managed sessions to `dontAsk`, and answers permission requests with `reject_once` or cancellation in both ACP read loops. Unattended operations that require interactive approval now fail closed instead of being silently authorized. Explicit non-interactive modes that do not bypass a permission request remain available. Both layers have to change together: `apply_permission_mode` treats an unsupported mode and a failed `set_config_option` as non-fatal by design, so a request can still reach the harness even in a non-interactive mode. Removing `bypassPermissions` from the enum rather than only changing the default means the mode cannot be restored by configuration alone. The scope of the guarantee is that `buzz-acp` never grants approval. An agent that pre-authorizes tools in its own configuration (for example Claude Code's `settings.json`) still runs them without asking, which is outside this harness. ## Testing - `env -u BUZZ_ACP_LAZY_POOL bin/cargo test -p buzz-acp` at `16fff4d`: 671 library tests and 9 integration tests passed - `cargo clippy -p buzz-acp --all-targets -- -D warnings` and `cargo fmt -p buzz-acp -- --check`: clean - `git diff --check origin/main...codex/security-acp-shell-auto-approval` The permission tests previously re-implemented the `reject_once` lookup in the test body instead of calling the code under test, so they would have passed unchanged if the harness went back to selecting `allow_once`. They could not call it directly, because `handle_permission_request` is a method on `AcpClient`, which owns a live `Child` and its stdio pipes. The choice is now a free function, `permission_denial_response`, and the tests exercise it: `reject_once` preferred over offered allow options, the cancelled fallback when no `reject_once` exists, an empty option list, and a `reject_once` missing its `optionId`. The cancelled fallback had no coverage before despite being the fail-closed backstop. ## Operator notes - `BUZZ_ACP_PERMISSION_MODE=bypassPermissions` no longer parses, so a process configured with it fails to start rather than silently downgrading. - Desktop managed agents do not set a permission mode, so they inherit `dontAsk`. The desktop has no permission prompt, so operations needing approval now fail with no in-app way to approve them. Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
885bed35ee |
fix(workflow): bind trigger author to the signed event (#4607)
This change derives `trigger_author` exclusively from the signed event pubkey. Actor tags remain available as event data but cannot override the identity used by author-sensitive workflow conditions. This removes the impersonation path without changing workflow definitions or requiring stored-data migration. ## Testing - `bin/cargo test -p buzz-workflow` at `78819df`: 154 passed, 2 Postgres-dependent tests ignored - `git diff --check origin/main...codex/security-workflow-trigger-author` Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
997b8caaa4 |
fix(git): revoke access for banned relay members (#4608)
This change rechecks the durable community ban in the shared Git HTTP authentication path for advertise, fetch, and push requests. A banned member is denied even if repository-channel membership still exists, and restriction lookup errors fail closed. The additional database lookup happens on every Git HTTP request so access revocation does not depend on stale session state. The check also cascades to the NIP-OA owner. Git accepts NIP-OA attestations on the NIP-98 token, so an agent key can act for its owner — without the cascade, a banned human would keep clone and push access through any agent key. This mirrors the NIP-42 gate in `handlers::auth`: either principal's ban denies the request. The check runs inside the `GitAuth` extractor, so all three Git routes inherit it. ## Testing - `git diff --check origin/main...codex/security-ban-revokes-git` - Rebased onto `origin/main` at `5c98932` - `cargo test -p buzz-relay --lib sec005_read_gate_tests`: 8 passed, 7 ignored (Postgres) - `cargo clippy -p buzz-relay --all-targets -- -D warnings` and `cargo fmt --check`: clean Pure tests cover the decision table (agent ban, inherited owner ban, no attestation). Postgres-gated tests cover the wiring: the real ban row, a live `compute_auth_tag` attestation, and the 503 fail-closed path. **Not yet verified:** the three Postgres-gated tests compile and skip but have not been run — no local Postgres, and CI does not run `--ignored`. They need `cargo test -p buzz-relay --lib sec005_read_gate_tests -- --ignored` against a migrated dev database. Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
35fcdc6e44 |
test(readState): add mutation-verified near-overflow witnesses for mark-all gate and frontier-ordering
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
8a7eb8d3d7 |
fix(agent): recover from unsupported image input instead of poisoning the turn (#4896)
## Problem
`buzz-dev-mcp` advertises `view_image` to every agent regardless of
whether the session's model accepts images. When a text-only model (e.g.
DeepSeek V4 Flash) takes the bait, the image lands in session history
and every subsequent LLM request 404s with `No endpoints found that
support image input`. The error was classified as `LlmModelNotFound` and
propagated fatally out of the turn loop — history stays poisoned,
buzz-acp retries the batch with exponential backoff, and the session
burns its entire clock doing no work. In a recent trial run, **all 57
trials that called `view_image` on a text-only model died this way; none
recovered.**
## Fix
Capability-gating the advertised tool isn't reliable — there is no
image-capability metadata at the agent layer across providers. Instead,
recover at the turn loop:
- **Typed error**: new `AgentError::UnsupportedImageInput`, classified
narrowly on the exact provider phrase `No endpoints found that support
image input` on both the generic 404 path and OpenRouter's 404 path.
Unknown-model 404s and OpenRouter parameter-routing 404s keep their
existing classifications. No deterministic retry.
- **In-turn recovery**: on this error, `RunCtx::run` strips every image
block from history — keeping the tool result (and therefore
tool-call/result pairing) intact — marks the result `is_error`, appends
actionable model-facing guidance ("The current model does not support
image input. The image was removed from conversation history so this
turn can continue. Use a text-based inspection tool…"), and continues
the same turn. Base64 never replays again.
- **Loop guard**: recovery only fires when at least one image was
removed; if the provider says "image" and history has none, the error
propagates as before.
## Tests
- Unit: phrase classification (typed, not retried; unknown-model 404
unaffected), idempotent image-to-error history mutation preserving call
IDs and text.
- End-to-end (`fake_llm.rs` + `fake_mcp.rs`): tool call → MCP image
result → 404 unsupported-image → same-turn recovery. Captured requests
prove round 2 carried the image, round 3 replays no image, carries the
guidance text, preserves pairing, and ends `end_turn`.
- Loop guard: typed unsupported-image error with **no** image in history
fails after exactly one provider request instead of spinning —
mutation-testing showed deleting the `removed == 0` guard survived the
suite, and `max_rounds` defaults to unlimited in production, so this
branch needed direct coverage.
Verified at `a210305019b33d5f56677b4c82bab79e4ac52d24`: `cargo test -p
buzz-agent` (full package, 381 unit + all integration suites) green;
`clippy --all-targets -D warnings` green; `fmt --check` green; pre-push
hooks (rust-tests, desktop-tauri-checks, branch-skew) green.
**Scope of the classification guarantee**: the classifier runs in the
shared `post()` (which Anthropic and OpenAI paths route through) and in
`openrouter_post()` — i.e., every 404 path in `llm.rs`. It only runs on
404 responses; providers that reject images with a different status
(e.g. a 400) are out of scope for this PR — see the review-comment
discussion for why broadening the phrase list alone would not cover
them.
Authored by Wren, loop-guard test by Sami, reviewed by Eva.
---------
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
|
||
|
|
948c63233a |
test(readState): rewrite markAllChannelsRead witnesses to bite production call paths
Replace the empty-loop stale test and refused-clear test with versions
that mount non-empty channels and create real registers via markChannelUnread.
- markAllChannelsRead stale: mounts scope A with channels=[channel-1] and a
ready manager, calls markChannelUnread to create a forced-unread entry in
forcedUnreadStore, switches to scope B, fires the stale A callback. Asserts
forcedUnreadStore key for A survives. Without isScopeLoaded() guard, the
stale callback reaches forcedUnreadStore.write(pubkeyA, {}) and wipes A's
entry (mutation (a) verified: deleting the guard fails this test).
- markAllChannelsRead accepted path: mounts with channels=[channel-1] + ready
manager, calls markChannelUnread to populate unreadChannelIds via liveness,
then markAllChannelsRead. Asserts forced entry deleted and observed evidence
cleared. Exercises the non-empty transition loop end-to-end.
- markChannelRead frontier-advance-before-cbump: adds channels=[channel-order]
and calls markChannelUnread first to create a real register before marking
read, exercising the with-register overrideCleared path.
Mutation-test coverage documented in file header:
(a) delete isScopeLoaded() fence-first guard -> stale test FAILS (verified)
(b) remove overrideCleared gate in mark-all -> NOT independently bitable
(applyOverrideRead always returns overrideCleared with normal counters)
(c) swap frontier-advance/C-bump order -> NOT independently bitable
(C-bump alone deactivates {s:1,c:0,b:0} regardless of order)
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
067c085f37 |
Define private managed agent wire protocol (#4593)
## Summary - reserve kind `30179` for owner-private managed-agent aggregates - define the fail-closed owner-self NIP-44 v2 envelope and versioned payload codec - bind runnable identity/configuration to complete signed `30175`/`30177` recovery projections - validate NIP-OA owner→agent attestations and reject self-attestation - document NIP-PMA authority, migration prerequisites, privacy, and deployment order - keep generic relay ingest closed until private storage and atomic aggregate CAS exist ## Safety boundary This is the inert protocol/codec slice only. It does not publish secrets, change agent authority, migrate local records, or enable kind `30179` ingestion. The relay regression test proves generic EVENT ingest still rejects the kind. The finalized migration plan adds later prerequisites for relay-private storage/CAS, runtime lease/fencing, Desktop cutover, and harness authentication. Those belong in staged follow-up PRs rather than expanding this inert foundation. ## Validation At commit `67f0ea4ebb8d3ccba3a3eb9374e89a7178913f74`: - `cargo test -p buzz-core` — 246 unit + 2 doc tests passed - `cargo test -p buzz-relay private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists` — passed - push hooks: Rust tests and desktop checks passed (`2145` desktop tests passed, `14` ignored) - `cargo fmt --all -- --check` - `git diff --check` ## Review Princess Donut cleared security/data integrity with no remaining high/medium findings. Mongo cleared migration compatibility and wire grammar. The later runtime lease/fencing protocol was also adversarially cleared as a plan; implementation slices still require independent evidence before activation. Deterministic plaintext/signed-projection/auth-tag interoperability vectors remain a valuable follow-up, not an S0 merge gate; random NIP-44 ciphertext is intentionally not snapshotted. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
dc17965c79 |
fix(mobile): serialize channel sections sync (#3165)
### What changed? Serializes channel-section relay synchronization so a late relay `CLOSED` cannot overlap an in-flight retry and install duplicate subscriptions. Pending subscription results are invalidated and immediately closed when the manager is disposed or superseded. ### Why? The startup retry added in #3004 could race with a late `CLOSED` or manager disposal, leaking an untracked live subscription. This keeps retry recovery single-flight and makes the lifecycle boundary explicit. ### How is it tested? Build and run. Added tests: - [`ChannelSectionsManager`](https://github.com/block/buzz/tree/main/mobile/test/features/channels/channel_sections/channel_sections_manager_test.dart) interleaving coverage for in-flight retry serialization and disposal during subscription setup *🤖 This PR was authored with a Buzz agent.* Signed-off-by: npub15w828kxsxu2684ynste0uah2jwkgatd99flt7ds4523hzm8ju6cshdr8hh <a38ea3d8d03715a3d49382f2fe76ea93ac8eada52a7ebf3615a2a3716cf2e6b1@buzz.block.builderlab.xyz> Co-authored-by: npub15w828kxsxu2684ynste0uah2jwkgatd99flt7ds4523hzm8ju6cshdr8hh <a38ea3d8d03715a3d49382f2fe76ea93ac8eada52a7ebf3615a2a3716cf2e6b1@buzz.block.builderlab.xyz> |
||
|
|
0c5435a430 |
fix(readState): structural seam fix — fence-first clear transitions, helper owns no evidence maps
Fixes the 2 CRITICAL + 2 IMPORTANT findings from Thufir's second corrective
review (
|
||
|
|
6dbc946512 |
fix(desktop): make missing-command error actionable for released builds (#4802)
User-facing error for missing ACP harness commands has been pointing released-build users to run `cargo build --release --workspace` and read TESTING.md — both dead ends for anyone not building from source. Updated message acknowledges that antivirus software can quarantine bundled binaries and provides practical remediation steps. Preserves pointer to TESTING.md for source builds. Fixes issue context from #4491. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> |
||
|
|
8342dfcc58 |
chore(release): release Buzz Desktop version 0.5.5 (#4809)
## Buzz Desktop release v0.5.5 - **Frozen main:** `25a9cf1be6d245fbd7373cb1160dbc790baf5bd5` - **Reviewed candidate:** `8380c1f8ead8816bcf1f4ea9f66aa08e2441b15a` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com>desktop-v0.5.5 |
||
|
|
25a9cf1be6 |
feat: paste composer text without formatting (#4801)
## Summary - handle Cmd+Shift+V on macOS and Ctrl+Shift+V on Windows/Linux in the message composer - read plain text through the native Tauri/arboard clipboard path in packaged builds, with a browser-only Clipboard API fallback - re-enter ProseMirror's paste pipeline with populated `text/plain` clipboard data so selection, undo, multiline behavior, and paste observers remain intact - cover both platform mappings with rendered composer E2E tests that assert the native command path ## Testing - `pnpm test` — 4,286 passed - `pnpm check` - `pnpm typecheck` - `pnpm exec playwright test composer-selection-formatting.spec.ts --project=smoke` — 26 passed - `cargo check --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets --target aarch64-apple-darwin` - `just desktop-tauri-test` — 2,206 core tests plus integration and doc-test groups passed - full pre-push hooks passed ## Manual verification Physical packaged-app clipboard verification remains recommended on macOS, Windows, and Linux. The automated E2E uses mocked Tauri IPC but asserts the native `read_clipboard_text` command is invoked. Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
79c52166cf |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4808)
Reverts block/buzz#4800 |
||
|
|
a0ed13de14 |
chore(release): release Buzz Desktop version 0.5.5 (#4800)
## Buzz Desktop release v0.5.5 - **Frozen main:** `4a2305170eef565bf1836e2859247e67c030f8af` - **Reviewed candidate:** `2d03d37b05b68186b2caad9da79080032be3ac72` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
dde0aac1a7 |
fix(readState): gate NIP-RS clear transitions and observer fallback authority
Four correctness defects in the
|
||
|
|
4a2305170e |
fix: reauthenticate databricks model discovery (#4008)
## Summary - preserve Databricks catalog 401 responses as authentication failures and retry discovery exactly once after silently refreshing the rejected bearer - preserve runtime OAuth recovery: when discovery has no usable OAuth credential, `session/new` succeeds with only the trimmed configured model so the first `session/prompt` can run the existing browser PKCE flow - reject a rejected configured `DATABRICKS_TOKEN` with actionable, non-interactive guidance; static credentials cannot recover through PKCE - use the configured-model fallback for non-auth discovery failures without caching failed or fallback catalogs, so later sessions retry discovery - keep known Databricks v2 models only for authenticated empty-catalog responses and mark their provenance - resolve discovery before MCP spawn or session registration, preventing failed discovery from leaking resources or consuming session capacity - permit serialized interactive PKCE only from the explicit saved-agent model picker; passive draft discovery never opens a browser ## Runtime flow 1. OAuth discovery attempts cached credentials and silent refresh without opening a browser. 2. If no usable OAuth bearer exists, `session/new` advertises only the configured model and succeeds. 3. The first `session/prompt` uses `TokenSource::bearer()`, which may launch browser PKCE. 4. A later session retries discovery and caches only the authenticated catalog. ## Regression coverage - rejected-but-locally-fresh OAuth bearer performs one refresh and one catalog retry - OAuth mode with no cached token allows `session/new` and returns exactly the trimmed configured model - the OAuth fallback is not cached; a later authenticated session retries discovery and caches the returned catalog - rejected static tokens still reject `session/new` - failed discovery does not consume the sole session slot or spawn the supplied MCP process - Desktop interactive/passive auth intent, static-token redaction, and authenticated empty-catalog provenance ## Verification - `cargo test -p buzz-agent` - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib commands::agent_models` - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings` - `cargo fmt --all -- --check` - `git diff --check` - full pre-push hooks ## Review Adversarial review found and drove fixes for session/MCP resource leakage, duplicate concurrent PKCE flows, sensitive error propagation, incorrect 403 reauthentication, missing discovery-level coverage, passive browser launch, and the Desktop file-size ratchet. The final follow-up preserves the existing prompt-time OAuth flow while retaining static-token rejection and pre-allocation discovery ordering. --------- Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
8faf09f9ae |
Revert "chore(release): release Buzz Desktop version 0.5.5" (#4797)
Reverts block/buzz#4788 |
||
|
|
a1d78f2959 |
feat: Buzz entity links — rich preview cards + in-app navigation for repos, PRs, and issues (#4695)
## Summary
Gives Buzz-hosted git entities the same "GitHub-style" chat experience
GitHub links already get: rich preview cards, real titles, and
click-through — except clicks navigate **in-app** to the Projects view
instead of a browser.
- **Spec**: `docs/buzz-entity-links.md` — link scheme, slices, and
deferred work (`buzz://project`, OS deep links, web routes).
- **Canonical `buzz://` deep links**: new
`desktop/src/shared/lib/entityLink.ts` with builders + strict parser for
`buzz://pr?id=…&owner=…&d=…`, `buzz://issue?…`, and
`buzz://repo?owner=…&d=…`, mirrored by a Rust module
(`crates/buzz-cli/src/links.rs`) with a shared golden-format test so the
two implementations can't drift.
- **Preview cards**: `linkPreview.ts` recognizes `buzz://` entity links
*and* HTTPS relay clone URLs (`{origin}/git/<pubkey>/<repo>`, the shape
agents paste today). Both normalize onto the canonical `buzz://` href,
so the two spellings of a repo dedupe to one `Buzz`-provider card
(`BuzzMark` logo) rendered by `link-preview-attachment.tsx`.
- **Title enrichment**: PR/issue cards fetch the real subject from the
relay event (`subject` tag or first content line) via
`useResolvedLinkPreviews.ts`; the cache is community-scoped and reset in
`resetCommunityState()`.
- **In-app navigation**: clicking a card or inline anchor (including
HTTPS relay clone URLs whose origin matches the active relay) routes to
the canonical `30617:<owner>:<d>` coordinate via `goProject()`
(`markdown/entityLinks.tsx`). **Merge dependency: #4671 must merge
first** — route resolution for `30617:` coordinates is implemented on
that branch (`feat/multi-repository-projects`). Entity-link and
external-anchor logic were extracted out of `markdown.tsx` to stay under
the file-size ratchet.
- **Agent side**: `buzz pr open`, `buzz issues create`, and `buzz repos
create` now return a ready-made `link` field (omitted when the relay
returns `accepted: false`), and `base_prompt.md` instructs agents to
paste it verbatim when announcing work.
## Test plan
- [x] Desktop unit tests: pass, including new `entityLink.test.mjs` and
`linkPreview.test.mjs` coverage (golden formats, malformed-link
rejection, clone-URL/`buzz://` dedupe, origin-gated anchor behavior,
label-must-win invariant, cache epoch)
- [x] Rust: `cargo test -p buzz-cli` golden-format test +
accepted/rejected link guard assertions, clippy + fmt clean
- [x] Biome + `tsc --noEmit` clean; pre-push hooks
(desktop-tauri-checks, rust-tests, desktop-test) pass
- [ ] Manual: paste a relay clone URL and a `buzz://pr` link in a
channel — verify one card each, real PR title, and in-app navigation to
the Projects view
Related: [#4671](https://github.com/block/buzz/pull/4671)
---------
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
|
||
|
|
4c665aeac3 |
fix(desktop): serialize tray channel actions for frontend (#4762)
## Summary - serialize native `openChannel` tray actions with the camelCase field names consumed by the TypeScript frontend - prevent a valid tray channel ID from becoming `/channels/undefined` - add a Rust serialization contract test covering the complete frontend payload shape ### Root cause `TrayAction` renamed the enum variant to `openChannel`, but its struct fields still serialized as `channel_id` and `community_generation`. The frontend reads `action.channelId`, so tray navigation called `goChannel(undefined)`. ### Testing - manually verified the corrected runtime payload and tray navigation before removing temporary logging - `just desktop-ci` - pre-push hooks (desktop checks/tests, Tauri checks, and Rust tests) --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
b948c54792 |
chore(release): release Buzz Desktop version 0.5.5 (#4788)
## Buzz Desktop release v0.5.5 - **Frozen main:** `383d9e1eafd569b44b9c835200dba69ef7cec9dc` - **Reviewed candidate:** `ac589061ef1009f55384536e483cfe9b1260697b` - **Previous desktop release:** `desktop-v0.5.4` - **Proposed immutable tag:** `desktop-v0.5.5` This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current `main`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag. The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com> |
||
|
|
e30db7028f |
feat(projects): support multiple repositories (#4671)
## Summary - adopt the finalized NIP-MP project model so one project can enumerate and switch between multiple NIP-34 repositories - add project and repository navigation, activity summaries, existing-repository attachment, and repository access-channel management - preserve privacy-safe activation provenance for agent-authored patches, pull requests, issues, and associated commits ## Test plan - [x] Run desktop typecheck and unit tests - [x] Run focused NIP-MP, repository access, and provenance tests - [x] Run Rust formatting and desktop lint checks - [x] Run the complete pre-push suite after merging current `main` - [ ] Manually verify project creation, repository attachment, switching, and access repair on staging - [ ] Manually verify public-channel and private-agent origin labels on newly created Git activity Related: [#4695](https://github.com/block/buzz/pull/4695) --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
383d9e1eaf |
fix(ci): make desktop cache test version agnostic (#4791)
## Summary - derive the current desktop package version in the release cache-key contract test - mutate that version in both `Cargo.toml` and `Cargo.lock` instead of assuming `0.5.4` - prevent desktop release version bumps from failing generic CI ## Context PR #4788 bumped Desktop to `0.5.5`, exposing the hard-coded fixture. The dedicated release candidate check passed, while generic CI failed with `desktop version changed cache key`. ## Verification - pre-commit hooks passed - pre-push hooks passed - CI will validate the full contract Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
7bcfe7e0a1 |
fix(desktop): widen post-Enter timeouts in empty-edit-delete spec (#4792)
## Summary Increases three Playwright assertion timeouts in `tests/e2e/empty-edit-delete.spec.ts` from 5s to 10s to fix a shard-composition flake introduced by PR #4694. ## Root Cause PR #4694 added `huddle-transcription.spec.ts` (477 lines, 22+ tests) to the Desktop Smoke E2E suite, shifting shard 2 composition so that `empty-edit-delete` now runs with significantly more accumulated browser state. The three affected assertions all wait for a React state update triggered by pressing Enter in edit mode: - `alertdialog` becoming visible after an empty edit (tests 1 and 2) - `edit-target` hiding after a successful non-empty edit (test 3) These transitions go through the React scheduler. In isolation they complete in milliseconds. In a loaded headless shard with accumulated GC pressure, the 5s window became insufficient — test 3 failed 3/3 times in CI run [30946444168](https://github.com/block/buzz/actions/runs/30946444168) with `edit-target` still visible after Enter. No product code is changed. The empty-edit-delete flow is correct and untouched by #4694. This is a test-environment timing adjustment only. ## What Changed - `tests/e2e/empty-edit-delete.spec.ts` — three `{ timeout: 5_000 }` → `{ timeout: 10_000 }` for the post-Enter React-update waits ## Validation - `just desktop-check` — passed - `just desktop-test` — 4194 passed, 0 failed Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
384ebc9d75 |
Merge commit 'ce3cf3cd2' into duncan/nip-rs-unread-format
* commit 'ce3cf3cd2': (76 commits) Polish Huddle voice controls (#4694) fix(local-archive): default both archive settings to enabled (#4750) fix(mobile): stop oversized read-state retry loop (#4595) fix(desktop): close reconnect gaps that previously required CMD+R (#4737) Dock Buzz Term within channel workspace (#4724) perf(relay): index channel-id lookups and skip trace-only reads (#4647) fix(agents): canonicalize stale persona harness pins (#4631) Refine community invite links (#4734) feat(desktop): persist sidebar observed-unread across webview reload (#3976) feat(desktop): surface config diff in restart-required badge (#3637) Polish sidebar unread hierarchy (#4573) fix(desktop): show cached display names on startup (#3317) docs(acp): explain per-channel session model in base prompt (#4729) docs(nip-am): normative amendment — cache SHOULD/MUST + pricingIdentity + consumer cost guidance (#4632) Remove blur from Welcome composer guidance (#4691) Refine desktop timeline activity presentation (#4582) Defer desktop media uploads until send (#4522) fix(desktop): stop clipping focus ring on channel intro action cards (#2392) (#4374) Polish mobile inbox and media flows (#4512) feat: ship Buzz Term (#4347) ... Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |