spawn_event_sync returns Result<(), String> so dispatch failure can be captured
by the workspace-apply post-commit section rather than being silently ignored.
The value is always Ok(()) since tauri::async_runtime::spawn is infallible; this
establishes the typed interface for future signaling.
try_regenerate_nest returns Result<(), String> instead of swallowing errors.
All fire-and-forget callers updated to .ok() to explicitly discard the Result.
apply_workspace post-commit:
- try_regenerate_nest moved out of the spawn_blocking closure into the async
post-commit section so its Result can populate the degraded vec.
- spawn_event_sync Result captured; dispatch failure pushed to degraded.
- Nest failure reported as 'nest context regeneration failed: ...' degradation.
useCommunityInit.ts: post-commit degraded items now emit a toast.warning (8 s)
via sonner so the user sees partial failures. Previously only console.warn.
AppShell.tsx: useManagedAgentRuntimeReconciliation key changed from
String(reinitKey) to `${activeCommunity?.id}-${reinitKey}`. A same-relay
identity swap (new communityId, unchanged reinitKey) now correctly re-triggers
runtime reconciliation. Destructured activeCommunity and reinitKey from
communitiesHook and updated two other call sites for consistency.
dead pub use exports in migration.rs removed (fold, backfill, detach, strip,
materialize — all now accessed only through scoped _in_dir/_at variants).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Nine files were over the gate limit after the workspace-scoped store
implementation. Trims/extractions to get all under limit:
- app_state_tests.rs: move scope lifecycle tests to app_state_scope_tests.rs
- app_state.rs: move pending_owned_channels methods to identity_storage.rs
- AppShell.tsx: inline reconciliation key as String(reinitKey) (1 line vs 3)
- tauri.ts: type alias ApplyWorkspaceResult + biome-ignore format to keep
the applyCommunity body under the limit
- runtime.rs: restructure scope capture to save a net line
- storage.rs: trim doc comments on _at variants to single-liners
- mesh_llm.rs: make scope_impl pub(crate) mod; fold scope relay capture
inside check_mesh_runtime_relay_scope; remove verbose comments
- migration.rs: extract scoped migration helpers to migration_scope.rs via
include!(); trim SHARED_AGENT_FILES/DIRS block comments
- migration_tests.rs: trim explanatory comments to save net 33 lines
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Phase 3 of workspace-scoped agent store:
3a: reconcile_managed_agent_runtimes loses its `communities` parameter.
The backend derives the sole target relay from the captured active scope;
cross-scope fan-out is no longer representable at the API level.
- runtime_commands.rs: capture active scope relay; remove communities Vec
- runtime_types.rs: remove ManagedAgentCommunityTarget struct
- tauriManagedAgents.ts: reconcileManagedAgentRuntimes() takes no args
- managedAgentRuntimeHooks.ts: bootstrapManagedAgentRuntimePairs calls
parameterless reconcile; drop communities list construction
- useManagedAgentRuntimeReconciliation.ts: rewritten to track a single
activeCommunityKey instead of per-relay state; simplified retry logic
- AppShell.tsx: pass `${activeCommunity?.id}-${reinitKey}` as the key
3b: Mesh relay-match reuse rule + fail-closed serve preflight + watchdog.
- mesh_llm.rs: ensure_relay_mesh_for_record captures scope relay at entry;
a live runtime is only reused when its relay matches the scope relay;
serve-mode mismatch fails closed with a precise 'Share Compute is
currently pinned to <relay>' error; client-mode mismatch falls through
to re-arm; drain_mesh_client_if_stale drains a client whose relay
differs from the incoming workspace relay (Layer-1 async, non-fatal).
- recovery.rs: rearm_relay_mesh_for_running_agents captures one scope per
pass; Live early-return only taken on relay match; serve-mode Live
mismatch skips the pass (machine-level pinning).
- personas.rs: add scoped load_personas_at / save_personas_at variants.
3c: Drain journal + compensation + apply_workspace rewrite.
- runtime_commands.rs: DrainJournalEntry struct, drain_scope_runtimes
(snapshot journal + stop all live runtimes, returns stopped/remaining/
first_error), compensate_drain (restart exactly the stopped entries).
- workspace.rs: apply_workspace return type changed from () to
WorkspaceApplyResult. Layer-1 async drains the Mesh client before
spawn_blocking. Drain stage acquires managed_agent_runtime_transition,
calls drain_scope_runtimes; on failure calls compensate_drain and
returns applied:false. Per-transition restore replaces the launch-only
managed_agent_restore_pending one-shot. Post-commit failures (event
sync, restore) surface as degraded entries on WorkspaceApplyResult.
3d: Scope-tagged runtime map entries.
- runtime_types.rs: ManagedAgentPairRuntime gains scope_id: Option<String>
- starting() constructor takes scope_id; captured from active scope at
spawn time in runtime_commands.rs, restore.rs, and runtime.rs.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
## Summary
- Add a **macOS-only** monochrome Buzz menu-bar icon with **Running**
and **Recent** agent sections.
- Show each agent as one selectable macOS row with `Name · elapsed` and
its channel underneath.
- Keep completed work available for quick channel re-entry, alongside
New Channel, Open Buzz, and Quit Buzz.
- Keep the main window alive on close and restore it from both the menu
bar and the macOS Dock.
- Fence queued channel actions by community generation so an in-flight
action from the previous community cannot navigate the newly selected
community.
- Leave Windows and Linux unchanged; cross-platform tray lifecycle
support can follow with platform-specific validation.
<img width="812" height="760" alt="Buzz macOS agent menu"
src="https://github.com/user-attachments/assets/19bfd874-8f06-4496-bdda-7ed2e7b5733f"
/>
## Validation
- `cargo fmt --check`
- Desktop Tauri suite: 1,860 passed, 14 ignored after merging current
`main`
- Desktop tests: 3,769 passed
- Desktop lint, file-size, text, and pubkey checks pass (two
pre-existing informational template-literal notices)
- Regression coverage verifies stale `OpenChannel` actions are discarded
across community changes while `NewChannel` survives
## Manual validation remaining
A native macOS smoke test is still requested before merge: menu
appearance, elapsed updates, Running → Recent, channel navigation, New
Channel, minimized/closed/Dock restore, Open Buzz, and Quit. E2E stubs
the tray IPC and does not exercise the native menu.
---------
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>
🤖
## Summary
- Keep the relay reconnect notification dismissed across repeated
connection retries during one continuous outage.
- Re-arm the notification after recovery or relay lifecycle replacement,
including switches between communities that use the same relay URL.
- Preserve the existing dedicated path for authentication and other
application-level errors.
### How an outage is tracked
The AppShell-owned relay-card hook treats an outage as one contiguous
runtime episode rather than assigning it a persisted ID. A hook-local
`outageActiveRef` is armed by the first qualifying unreachable/degraded
observation. While it is armed, intermediate retry states (`connecting`,
`reconnecting`, `stalled`, and `disconnected`) belong to that same
episode, so retry churn cannot clear dismissal or emit another
notification.
The hook receives the same lifecycle identity used by community
initialization: community ID plus `reinitKey`. This distinguishes
multiple communities even when they share a relay URL, and it also
changes when the active community is explicitly reinitialized. The latch
and dismissal are reset when that identity changes or when the relay
singleton reports its authoritative `idle` teardown state. A successful
`connected` state also closes the episode and re-arms the next outage.
These boundaries deliberately bias toward re-notifying rather than
suppressing a later outage: recovery, community switch/reinit, or relay
teardown cannot leave the hook stuck believing an old outage is still
active. No outage state is persisted beyond the mounted hook lifecycle.
### Related issue
None found.
### Testing
- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop test` — 3,769 passed
- `pnpm --dir desktop check`
- `pnpm --dir desktop build:e2e`
- `pnpm --dir desktop exec playwright test
tests/e2e/sidebar-relay-card.spec.ts --project=integration` — 11 passed
---------
Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
Co-authored-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
## Why
The Inbox mixed overlapping feed categories with personal work queues,
so **All** was not actually comprehensive and several filters did not
make it clear why an item appeared. Threads and DMs could produce one
row per event instead of one row per conversation, drafts were hidden
until selected, and reminders appeared through multiple competing
presentations.
This refactor makes the **Inbox** a focused, conversation-oriented place
to catch up on work relevant to you. It is intentionally not a mirror of
every unread event in every channel.
## What changed
- Keep the destination named **Inbox** and use the standard Lucide bell
icon.
- Refocus **All** on DMs, mentions, thread replies, needs-action items,
replies from agents the user owns or controls, due reminders, and active
drafts.
- Exclude generic top-level channel traffic and updates from agents the
user does not own or control.
- Group each thread or DM into one row, sorted by latest activity.
- Resume an unread conversation at its oldest unread message while
opening the full thread or DM in the detail pane.
- Reuse the existing **New** divider at the unread boundary.
- Make the detail title a direct link to the canonical conversation.
- Give Reminders and Drafts the same list/detail interaction and
location metadata as conversation rows.
- Separate Reminders and Drafts from message filters with a subtle
divider, without adding another labeled section.
- Put reminder and draft counts beside their corresponding filter labels
instead of on the generic filter button.
- Preserve the selected conversation when switching filters if it
remains valid; otherwise select a valid replacement without flashing
stale detail.
- Use filter-specific empty states and rename the options toggle to
**Show unread only**.
- Ship the focused behavior directly. The earlier experiment gate,
Custom view, and default-view controls have been removed from this PR to
keep the first pass focused.
## Filter model
| Filter | What appears |
| --- | --- |
| **All** | One row per personally relevant conversation, plus due
reminders and active drafts. Includes DMs, mentions, thread replies,
explicit needs-action items, and replies from agents the current user
owns or controls. Excludes generic top-level channel traffic, other
agents' updates, and reminders that are not due yet. |
| **Mentions** | Conversations containing a direct mention. Each
conversation appears once and opens with full context. |
| **Threads** | Conventional threaded replies, grouped to one row per
thread. Broadcast replies are not treated as conventional thread
replies. |
| **Needs action** | Feed items explicitly classified as requiring
action. |
| **Agents** | Conversations whose representative response was authored
by an agent the current user owns or controls, including top-level DM
responses. If a human replies afterward, the conversation leaves this
filter until an owned agent responds again. |
| **Reminders** | All pending reminders, including upcoming reminders
that stay out of **All** until they are due. |
| **Drafts** | Active drafts, ordered by their last real edit time. |
## Grouping, ordering, and state
- A thread or DM creates one Inbox row rather than one row per event.
- An unread conversation resumes at its oldest unread message so
intervening context is not skipped.
- Conversation rows still sort by their latest activity.
- The detail pane opens the full available conversation and shows the
shared **New** divider before the first unread message.
- Upcoming reminders appear only in **Reminders**.
- When a reminder becomes due, it enters **All** at its trigger time. If
its source conversation is already represented, the reminder state
merges into that row instead of creating a duplicate; otherwise it
appears as a standalone reminder row.
- A due reminder can enrich a row in another relative filter when that
conversation already qualifies for the filter. Reminder lifecycle
remains separate from message read state.
- Drafts appear in **All** by their last real edit time. Opening an
unchanged draft does not move it to the top.
- Reminder and draft rows show their location as `In #channel` or `In DM
with <name>`.
- **Show unread only** hides reminder and draft work queues because they
do not share message unread semantics.
## Removed or narrowed
- **Remove the old Activity filter.** It overlapped with All while still
omitting items All now includes.
- **Narrow Agents.** It no longer gathers every agent participating in a
shared thread or subsequent human follow-ups.
- **Remove duplicate reminder presentations.** The aggregate
pending-reminders jump and duplicate generic feed rows are replaced by
one list/detail model.
- **Remove Custom and default-view settings from this pass.** They added
considerable state and UI before the core model had been validated.
- **Do not add section labels for Reminders and Drafts.** A divider
communicates the distinction without creating another hierarchy in the
menu.
## Risk assessment
Medium implementation risk because this changes composition, grouping,
ordering, read behavior, and personal queues in a primary desktop view.
The implementation is scoped to the desktop UI and its local feed
projection; it does not change relay schemas or public APIs.
## Testing
- Desktop formatting, lint, file-size, text-size, and TypeScript checks
passed.
- Desktop unit suite: **3,663 passed, 0 failed**.
- Desktop E2E production build passed.
- Playwright smoke coverage across every spec touching this surface
(`channels`, `smoke`, `profile`, `project-inbox`, `community-rail`,
`integration`, `drafts-screenshots`): **118 passed, 0 failed**.
- Full Playwright smoke project: **732 passed, 1 skipped**. Three local
failures were investigated and cleared — `community-rail` keyboard
reorder passed on re-run (flaky), while `relay-reconnect:97` and
`video-attachment:223` are untouched by this commit (the only change to
shared `tests/helpers/bridge.ts` is a comment) and pass in CI.
- Unit coverage includes focused All matching, owned-agent filtering,
conversation grouping, oldest-unread selection, selection stability,
chronological reminder/draft composition, trigger-time reminder
ordering, and duplicate reminder suppression.
## Update: July 27, 2026
The naming decision is settled: the surface stays **Inbox**. An earlier
pass in this branch had renamed it to **Activity**; that rename has been
reverted in `9c00d2d6e`, which is naming-only and changes no behavior.
The revert covers file names, component/hook/type/constant identifiers,
the sidebar label and tooltip, the `Inbox options` and `Filter inbox:`
aria-labels, and the corresponding test names, test ids, and fixture
ids.
Three things were deliberately left as `activity`:
- **The feed API contract** — the `activity` / `agent_activity`
categories, the `feed.activity` and `feed.agentActivity` keys, and the
`types=` query parameter. These are the server's names, not the
surface's.
- **Plain-noun usage** — empty states such as "No activity yet", plus
`latestActivityAt` and `PROJECT_ACTIVITY_KINDS`.
- **Pre-existing agent, project, and profile activity code**, which
refers to a different concept entirely.
The earlier experiment-gate approach has also been dropped, so
`tests/helpers/bridge.ts` no longer claims that an Activity preview
feature exists — `preview-features.json` has no such entry and the seed
helper enables every desktop feature.
Generated with Codex
---------
Signed-off-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>