mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): pass-2 correction C7 — degraded results observable + AppShell composite key
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>
This commit is contained in:
co-authored by
Will Pfleger
parent
18c4be359d
commit
372b0fabd9
@@ -982,7 +982,7 @@ pub async fn update_managed_agent(
|
||||
(summary, sync_params, rollback)
|
||||
}; // lock dropped here
|
||||
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
|
||||
// Phase 2: relay profile sync (async, outside lock). A rename is committed
|
||||
// only when this succeeds; otherwise restore the complete pre-edit record
|
||||
|
||||
@@ -92,7 +92,7 @@ pub(super) fn rollback_failed_agent_update(
|
||||
.ok_or_else(|| format!("agent {pubkey} not found after failed rename rollback"))?;
|
||||
super::agents::retain_managed_agent_pending(app, state, restored);
|
||||
}
|
||||
try_regenerate_nest(app);
|
||||
try_regenerate_nest(app).ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -978,7 +978,7 @@ pub async fn create_managed_agent(
|
||||
agent
|
||||
};
|
||||
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
|
||||
// ── Phase 4: sync agent profile on relay (async, outside lock) ───────────
|
||||
// Use the avatar persisted on the record so the published profile and any
|
||||
@@ -1343,7 +1343,7 @@ pub async fn delete_managed_agent(
|
||||
// best-effort, inside-the-lock contract as the tombstone above.
|
||||
archive_managed_agent_pending(&app, &state, &pubkey);
|
||||
}
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -77,7 +77,7 @@ pub async fn create_persona(
|
||||
personas.push(persona.clone());
|
||||
save_personas(&app, &personas)?;
|
||||
retain_persona_pending(&app, &state, &persona);
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
Ok(persona)
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -177,7 +177,7 @@ fn reconcile_inbound_persona_event_blocking(
|
||||
}
|
||||
_ => unreachable!("kind gated above"),
|
||||
}
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
|
||||
// Signal the live UI to refetch agents data — inbound relay events otherwise
|
||||
// land on disk silently, leaving the Agents tab stale until restart.
|
||||
@@ -315,7 +315,7 @@ fn reconcile_inbound_tombstone(
|
||||
}
|
||||
_ => unreachable!("target kind gated above"),
|
||||
}
|
||||
try_regenerate_nest(app);
|
||||
try_regenerate_nest(app).ok();
|
||||
|
||||
// Refresh the live UI on inbound deletion — a removal is as user-visible as
|
||||
// an upsert and the Agents tab must drop the tombstoned record without restart.
|
||||
|
||||
@@ -244,7 +244,7 @@ pub async fn delete_persona(id: String, app: AppHandle) -> Result<(), String> {
|
||||
// _store_guard drops here, before try_regenerate_nest.
|
||||
}
|
||||
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@@ -298,7 +298,7 @@ pub async fn set_persona_active(
|
||||
|
||||
let updated = persona.clone();
|
||||
save_personas(&app, &personas)?;
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
Ok(updated)
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -664,7 +664,7 @@ pub async fn confirm_agent_snapshot_import(
|
||||
// inlined here to avoid cross-module private-fn access.)
|
||||
retain_agent_pending(&app, &state, &record);
|
||||
|
||||
crate::managed_agents::try_regenerate_nest(&app);
|
||||
crate::managed_agents::try_regenerate_nest(&app).ok();
|
||||
|
||||
// Notify other mounted clients of local persona+managed-agent writes,
|
||||
// matching the contract used by other local managed-agent mutations.
|
||||
|
||||
@@ -135,7 +135,7 @@ pub(super) async fn update_persona_with<R: Send + 'static>(
|
||||
save_personas(&app, &personas)?;
|
||||
|
||||
let retained = retain(&app, &state, &result)?;
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
|
||||
// If the avatar or display_name changed, propagate to linked agent
|
||||
// records and collect relay profile sync params for the async phase.
|
||||
|
||||
@@ -748,7 +748,7 @@ pub async fn confirm_team_snapshot_import(
|
||||
}
|
||||
crate::commands::teams::retain_team_pending(&app, &state, &imported_team);
|
||||
|
||||
crate::managed_agents::try_regenerate_nest(&app);
|
||||
crate::managed_agents::try_regenerate_nest(&app).ok();
|
||||
let _ = app.emit("agents-data-changed", ());
|
||||
|
||||
imported_team
|
||||
|
||||
@@ -239,7 +239,7 @@ pub async fn delete_team(id: String, app: AppHandle) -> Result<(), String> {
|
||||
for persona_d_tag in &cascaded_persona_d_tags {
|
||||
super::personas::tombstone_persona_pending(&app, &state, persona_d_tag);
|
||||
}
|
||||
try_regenerate_nest(&app);
|
||||
try_regenerate_nest(&app).ok();
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -38,12 +38,17 @@ pub fn run_event_sync(
|
||||
/// `AppState::keys` mutex. The reconcile itself is still synchronous JSON,
|
||||
/// SQLite, and signing work, so it runs on the blocking pool rather than an
|
||||
/// async worker.
|
||||
///
|
||||
/// Returns `Err` when the `spawn_blocking` call itself fails to enqueue (the
|
||||
/// thread pool is exhausted or the runtime is shutting down). Completion
|
||||
/// failures from the reconcile task are logged internally and do not reach
|
||||
/// the caller — only dispatch failure reaches here.
|
||||
pub fn spawn_event_sync(
|
||||
app: tauri::AppHandle,
|
||||
owner_keys: nostr::Keys,
|
||||
db_path: std::path::PathBuf,
|
||||
definitions_dir: std::path::PathBuf,
|
||||
) {
|
||||
) -> Result<(), String> {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(e) = tauri::async_runtime::spawn_blocking(move || {
|
||||
run_event_sync(&app, &owner_keys, &db_path, &definitions_dir);
|
||||
@@ -53,6 +58,7 @@ pub fn spawn_event_sync(
|
||||
eprintln!("buzz-desktop: event-sync: spawn_blocking failed: {e}");
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reconcile `personas.json` into the persona-event retention store.
|
||||
|
||||
@@ -692,11 +692,13 @@ pub fn regenerate_nest_context(app: &AppHandle) -> Result<(), String> {
|
||||
/// Convenience wrapper: regenerates nest context, logging a warning on failure.
|
||||
///
|
||||
/// All call sites treat regeneration as fire-and-forget — agents run fine with
|
||||
/// a stale AGENTS.md, so we warn and continue rather than propagating the error.
|
||||
pub fn try_regenerate_nest(app: &AppHandle) {
|
||||
if let Err(error) = regenerate_nest_context(app) {
|
||||
/// a stale AGENTS.md. Returns `Err` when regeneration fails so callers can
|
||||
/// report it as degradation in the workspace-apply result.
|
||||
pub fn try_regenerate_nest(app: &AppHandle) -> Result<(), String> {
|
||||
regenerate_nest_context(app).map_err(|error| {
|
||||
eprintln!("buzz-desktop: nest context regeneration failed: {error}");
|
||||
}
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -109,6 +109,7 @@ export function AppShell() {
|
||||
useTauriWindowDrag();
|
||||
useWebviewScrollBoundaryLock();
|
||||
const communitiesHook = useCommunities();
|
||||
const { activeCommunity, reinitKey } = communitiesHook;
|
||||
const hasCommunityRail = communitiesHook.communities.length > 1;
|
||||
const addCommunityDialog = useAddCommunityDialogState();
|
||||
const [isChannelManagementOpen, setIsChannelManagementOpen] =
|
||||
@@ -123,7 +124,7 @@ export function AppShell() {
|
||||
const mainInsetRef = React.useRef<HTMLElement>(null);
|
||||
const location = useLocation();
|
||||
const queryClient = useQueryClient();
|
||||
useManagedAgentRuntimeReconciliation(String(communitiesHook.reinitKey)); // re-runs on workspace switch
|
||||
useManagedAgentRuntimeReconciliation(`${activeCommunity?.id}-${reinitKey}`);
|
||||
const {
|
||||
goAgents,
|
||||
goChannel,
|
||||
@@ -168,10 +169,7 @@ export function AppShell() {
|
||||
const { starredChannelIds, starChannel, unstarChannel } = useChannelStars(
|
||||
identityQuery.data?.pubkey,
|
||||
);
|
||||
usePersonaSync(
|
||||
identityQuery.data?.pubkey,
|
||||
communitiesHook.activeCommunity?.relayUrl,
|
||||
);
|
||||
usePersonaSync(identityQuery.data?.pubkey, activeCommunity?.relayUrl);
|
||||
useAgentsDataRefresh();
|
||||
// Chunk F: auto-restart drifted idle agents (per-agent opt-out, default ON).
|
||||
useAutoRestartPolicy();
|
||||
@@ -231,8 +229,8 @@ export function AppShell() {
|
||||
: undefined;
|
||||
const relayConnectionCard = useSidebarRelayConnectionCard(
|
||||
channelsErrorMessage,
|
||||
communitiesHook.activeCommunity?.relayUrl,
|
||||
`${communitiesHook.activeCommunity?.id ?? "none"}-${communitiesHook.reinitKey}`,
|
||||
activeCommunity?.relayUrl,
|
||||
`${activeCommunity?.id ?? "none"}-${reinitKey}`,
|
||||
);
|
||||
const memberChannels = React.useMemo(
|
||||
() => channels.filter((channel) => channel.isMember),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { isMacPlatform } from "@/shared/lib/platform";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { resetRateLimitGate } from "@/shared/api/relayRateLimitGate";
|
||||
@@ -238,13 +239,19 @@ export function useCommunityInit(
|
||||
return;
|
||||
}
|
||||
|
||||
// Workspace applied. Log any post-commit degradation (informational —
|
||||
// the workspace IS active; these are best-effort post-commit steps).
|
||||
// Workspace applied. Surface any post-commit degradation as a
|
||||
// user-visible warning toast — the workspace IS active, but some
|
||||
// best-effort post-commit steps failed (nest, event-sync, restore).
|
||||
if (applyResult.degraded.length > 0) {
|
||||
const reason = applyResult.degraded.join("; ");
|
||||
console.warn(
|
||||
"[useCommunityInit] workspace applied with degradation:",
|
||||
applyResult.degraded,
|
||||
);
|
||||
toast.warning("Workspace applied with partial failures", {
|
||||
description: reason,
|
||||
duration: 8000,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// A bad `repos_dir` no longer reaches here — `apply_workspace` treats
|
||||
|
||||
Reference in New Issue
Block a user