Skip to content

feat(desktop): surface config diff in restart-required badge - #3637

Merged
wpfleger96 merged 1 commit into
mainfrom
hayt/restart-diff-ui
Aug 4, 2026
Merged

feat(desktop): surface config diff in restart-required badge#3637
wpfleger96 merged 1 commit into
mainfrom
hayt/restart-diff-ui

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 29, 2026

Copy link
Copy Markdown
Member

The "Restart required" badge reports that an agent's running config has drifted from its spawn-time config, but never says what changed. This ships the full feature: a typed Rust diff engine and a TS/UI layer that renders it at every badge site.

Rust core (spawn-snapshot diff engine)

Replaces the lossy u64 spawn_config_hash with a typed SpawnConfigSnapshot. The snapshot is stamped from the already-resolved command/env/config values immediately before spawn(), closing the race window where a mid-spawn config edit would suppress the badge.

SpawnConfigSnapshot::canonical() is the single JSON projection shared by the badge and the diff. Drift is to_value(stamped) != to_value(current); the diff is a generic leaf walk over those same two values, so badge-on and diff-non-empty are structurally guaranteed. Adding a snapshot field reaches the UI with no code change to the diff engine — mutation_table_covers_every_serialized_field fails CI if a new field arrives without a mutation row.

eligible_restart_diff(persona_orphaned, Option<TrackedSpawnState>) returns the final vector — snapshot walk entries plus a synthetic adapter_availability entry. It returns empty for an orphaned instance (spawning one would fail) and for agents with no tracked spawn state (never stamped, can never have drifted). needs_restart = !restart_diff.is_empty() derives from that vector and nothing else.

Redaction policy (policy_for(path)) is shared by the wire diff and the snapshot's manual Debug via is_safe_to_reveal() from managed_agents::env_vars as the single authority for env-key masking:

Policy Paths Rendering
Text system_prompt, team_instructions character counts only
MaskedBare args, relay_url ••••, no suffix
MaskedSuffix non-allowlisted env.* •••• + last 4 chars when longer than 8
Plain allowlisted env.* (BUZZ_AGENT_THINKING_EFFORT, BUZZ_AGENT_PROVIDER, BUZZ_AGENT_MODEL, DATABRICKS_HOST/MODEL) and everything else verbatim

Default-deny: every env key not in the explicit allowlist stays masked. is_safe_to_reveal() is the single allowlist authority for both the baked-env display and the diff.

restart_diff is omitted from the wire when empty (skip_serializing_if).

TypeScript / UI layer

New restartDiff.ts module defines RestartDiffEntry, RestartChange, JsonValue; tauri.ts and types.ts re-export and add restart_diff / restartDiff fields (Rust omission → restartDiff: []).

RestartDiffBadge — hover tooltip capped at 6 entries + "and N more", asChild span trigger (never inside a <button>), auto-restart blurb below the diff list (on/off variant from autoRestartEnabled prop; same AUTO_RESTART_ON_BLURB / AUTO_RESTART_OFF_BLURB constants shared with the Runtime-tab banner). RestartDiffList renders the full uncapped list for the Runtime-tab banner with tooltip/inline presentation variants for correct foreground in both surfaces.

ManagedAgentRow B4 fix — badge moved to a sibling div of the row expansion button; tooltip trigger has no button ancestor.

UnifiedAgentsSection — both badge sites render <RestartDiffBadge> instead of a raw <Badge>, with autoRestartEnabled threaded from agent.autoRestartOnConfigChange.

Side-panel fixRestartDiffBadge rendered tab-independently in the ProfileSummaryView hero area (was Runtime-tab only — root cause of the ~50% inconsistency Will reported). Hero badge is self-center in the flex column. ProfileRuntimeTabContent early-return checks needsRestart so the banner is never dropped when all other content is empty. Auto-restart blurb in the Runtime-tab banner uses the shared constants.

Wire shape

"restart_diff": [
  { "field": "model",              "change": { "kind": "value",  "before": "gpt-5", "after": "claude-4" } },
  { "field": "system_prompt",      "change": { "kind": "text",   "before_chars": 1234, "after_chars": 1410 } },
  { "field": "env.OPENAI_API_KEY", "change": { "kind": "masked", "before": "••••bc12", "after": "••••xyz9" } },
  { "field": "env.BUZZ_AGENT_THINKING_EFFORT", "change": { "kind": "value", "before": "medium", "after": "high" } }
]

added/removed occur only for dynamic-map keys; nullable struct fields always serialize as null; arrays are atomic leaves (args, never args.0).

Tests

Rust — 1902 passing: snapshot mutation coverage, diff entry serialization, allowlist-aware env masking (allowlisted_env_key_shows_plain_value, allowlisted_env_key_is_case_insensitive, non_allowlisted_env_key_stays_masked), unstamped_agent_yields_no_badge_and_no_entries (both orphan values), summary_without_drift_omits_restart_diff_from_the_wire, unstamped_availability_is_not_drift. Clippy clean, fmt clean.

TypeScriptneeds-restart-screenshots.spec.ts: 11 E2E cases registered in the smoke project — all three badge sites, tooltip + keyboard focus, DOM no-button-ancestor assertion, 6+1 truncation, uncapped Runtime list, unknown field humanisation, side-panel badge on default Info tab, inactive/friendly-error Runtime opening path.

Consolidates #3652

@wpfleger96
wpfleger96 requested a review from a team as a code owner July 29, 2026 22:52
@wpfleger96 wpfleger96 changed the title feat(desktop): restart-diff TS/UI — tooltip, banner, side-panel fix feat(desktop): surface config diff in restart-required badge Jul 30, 2026
@wpfleger96
wpfleger96 force-pushed the hayt/restart-diff-ui branch from 7715d7e to 551bbe1 Compare July 31, 2026 14:48
kalvinnchau
kalvinnchau previously approved these changes Jul 31, 2026
When a running managed agent's config drifts from what it was spawned
with, the UI now surfaces the specific before/after diff — field names,
old values, new values — rather than a bare "Restart required" badge.

Rust (desktop/src-tauri):
- New spawn_snapshot module: captures the effective config at spawn time
  as a structured snapshot (command, args, env, relay URL, system prompt,
  team instructions, session title, parallelism). The same structure
  feeds both the hash comparison (no behaviour change) and a field-level
  diff for the UI.
- spawn_snapshot/diff.rs: produces RestartDiffEntry vec from two
  snapshots. Policy-driven masking: env keys on is_safe_to_reveal()
  allowlist (BUZZ_AGENT_THINKING_EFFORT, BUZZ_AGENT_PROVIDER,
  BUZZ_AGENT_MODEL, DATABRICKS_HOST/MODEL) render plain; all other env
  keys are masked (key name visible, value shows last-4 chars).
  Big values (system prompt, team instructions) are summarised as char
  counts. Secrets are never leaked.
- Hoist is_safe_to_reveal() from commands/agent_config.rs to
  managed_agents/env_vars.rs as pub(crate) — single allowlist authority
  for both baked-env display and the diff masking policy.
- ManagedAgentSummary gains restart_diff: Vec<RestartDiffEntry> (empty
  when no drift or no stamp). Adopted PID-only agents have no stamp and
  always produce an empty diff — same invariant as the existing hash
  behaviour.
- Adapter-availability drift (codex) continues to set needs_restart;
  diff vec stays empty for that case.
- Tests: snapshot round-trips, diff field coverage, allowlisted /
  non-allowlisted masking, case-insensitivity, empty-key / dotted-key
  fail-closed, unstamped-agent invariant.

TypeScript / UI:
- New RestartDiffEntry / RestartChange / JsonValue types in restartDiff.ts;
  re-exported from tauri.ts and types.ts.
- fromRawManagedAgent maps restart_diff → restartDiff (omission → []).
- RestartDiffBadge: hover tooltip capped at 6 entries + "and N more";
  asChild span trigger — never nested inside a <button>. Shows
  auto-restart blurb (on/off variant) below the diff list.
- RestartDiffList: uncapped display for the Runtime-tab banner.
- Hero badge in UserProfilePanelSections is self-center (was self-start).
- AUTO_RESTART_ON_BLURB / AUTO_RESTART_OFF_BLURB exported constants
  shared by the badge tooltip and the Runtime banner; both surfaces
  import the same strings so they cannot drift.
- All four badge call sites (ManagedAgentRow, UnifiedAgentsSection ×2,
  UserProfilePanelSections hero) pass autoRestartEnabled.
- e2e: needs-restart-screenshots.spec.ts covers all badge sites, tooltip
  hover + keyboard focus, no-button-ancestor assertion, 6+1 truncation,
  full uncapped Runtime list, unknown-field humanisation, side-panel badge
  on default Info tab.
- types.rs doc comments trimmed to stay under the 1000-line ratchet.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 force-pushed the hayt/restart-diff-ui branch 2 times, most recently from 27420b4 to c611630 Compare August 4, 2026 15:48
@wpfleger96
wpfleger96 merged commit f86dfc5 into main Aug 4, 2026
26 checks passed
@wpfleger96
wpfleger96 deleted the hayt/restart-diff-ui branch August 4, 2026 16:22
wpfleger96 pushed a commit that referenced this pull request Aug 4, 2026
…-links-fixes

* origin/main:
  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)

Signed-off-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
tellaho pushed a commit that referenced this pull request Aug 4, 2026
…theme-config

* origin/main:
  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)

Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
tellaho pushed a commit that referenced this pull request Aug 4, 2026
…onfig

* origin/main:
  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)

Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Aug 4, 2026
…-phase2-integration

* origin/main: (23 commits)
  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)
  feat(mobile): sync per-group channel sorting (#4231)
  feat(mobile): add channel scroll navigation (#4239)
  feat(desktop): redesign the Huddle experience (#4281)
  feat(mobile): bring channel menus to desktop parity (#3940)
  feat(agents): model-tuning parity in global Agent Defaults editor (#4578)
  Polish Share Compute settings (#3735)
  fix(reactions): wrap long popover names (#3834)
  ...
wpfleger96 added a commit that referenced this pull request Aug 4, 2026
…opagation, tests

True git rebase onto bc9e652 (current origin/main). Conflict resolution
preserves #3637 (spawn_snapshot, restart-diff) and #4522 (media_raw, deferred
uploads) alongside all B1 content.

Fix 3b (hash-verified file recovery):
- schema.rs: add agents_content_hash / teams_content_hash columns to
  file_commit_phases (schema v3 with ALTER TABLE migration for existing DBs).
- txn.rs: compute SHA-256 of staged payloads and record them in the intent row.
- Recovery now verifies canonical file hash against recorded hash before
  treating absent stage files as 'already completed'. All three missing-stage
  branches (intent/both-absent, intent/teams-absent, first_renamed/teams-absent)
  fail closed unless canonical matches. Only hash-verified canonicals advance
  to committed.

Fix 4 (tombstone/archive propagation):
- agents.rs: tombstone_managed_agent_pending and archive_managed_agent_pending
  use .map_err(|e| ...)?  — failures surface in the delete command's Result.
- personas/mod.rs: same pattern for cascaded agent tombstones and persona tombstone.
- teams.rs: tombstone_team_pending and cascaded persona tombstones propagate.
- agents_retain.rs: retain_managed_agent_pending keeps its outer log-and-swallow
  with an explicit comment documenting the boot-reconcile-recoverable contract
  (Thufir accepted this distinction for updates vs deletes).

Tests:
- store_journal_fix_tests.rs (5 new tests):
  - test_file_recovery_rename_done_phase_not_updated_succeeds: both stages absent,
    canonicals match hashes → advance to committed.
  - test_file_recovery_hash_mismatch_fails_closed: hash mismatch → uncommitted.
  - test_file_recovery_first_renamed_teams_done_hash_verified: teams hash verified.
  - test_file_recovery_first_renamed_teams_absent_no_hash_fails_closed: fail closed.
  - test_boot_recovery_inserts_into_supplied_retention_path_not_flat: scoped path.
- storage_tests.rs (2 new tests): keyring round-trip (Fix 2 coverage).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 added a commit that referenced this pull request Aug 4, 2026
…opagation, tests

True git rebase onto bc9e652 (current origin/main). Conflict resolution
preserves #3637 (spawn_snapshot, restart-diff) and #4522 (media_raw, deferred
uploads) alongside all B1 content.

Fix 3b (hash-verified file recovery):
- schema.rs: add agents_content_hash / teams_content_hash columns to
  file_commit_phases (schema v3 with ALTER TABLE migration for existing DBs).
- txn.rs: compute SHA-256 of staged payloads and record them in the intent row.
- Recovery now verifies canonical file hash against recorded hash before
  treating absent stage files as 'already completed'. All three missing-stage
  branches (intent/both-absent, intent/teams-absent, first_renamed/teams-absent)
  fail closed unless canonical matches. Only hash-verified canonicals advance
  to committed.

Fix 4 (tombstone/archive propagation):
- agents.rs: tombstone_managed_agent_pending and archive_managed_agent_pending
  use .map_err(|e| ...)?  — failures surface in the delete command's Result.
- personas/mod.rs: same pattern for cascaded agent tombstones and persona tombstone.
- teams.rs: tombstone_team_pending and cascaded persona tombstones propagate.
- agents_retain.rs: retain_managed_agent_pending keeps its outer log-and-swallow
  with an explicit comment documenting the boot-reconcile-recoverable contract
  (Thufir accepted this distinction for updates vs deletes).

Tests:
- store_journal_fix_tests.rs (5 new tests):
  - test_file_recovery_rename_done_phase_not_updated_succeeds: both stages absent,
    canonicals match hashes → advance to committed.
  - test_file_recovery_hash_mismatch_fails_closed: hash mismatch → uncommitted.
  - test_file_recovery_first_renamed_teams_done_hash_verified: teams hash verified.
  - test_file_recovery_first_renamed_teams_absent_no_hash_fails_closed: fail closed.
  - test_boot_recovery_inserts_into_supplied_retention_path_not_flat: scoped path.
- storage_tests.rs (2 new tests): keyring round-trip (Fix 2 coverage).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 added a commit that referenced this pull request Aug 4, 2026
* 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>
shellz-n-stuff added a commit to shellz-n-stuff/buzz that referenced this pull request Aug 4, 2026
…gent-instructions

* origin/main: (30 commits)
  feat: Buzz entity links — rich preview cards + in-app navigation for repos, PRs, and issues (block#4695)
  fix(desktop): serialize tray channel actions for frontend (block#4762)
  chore(release): release Buzz Desktop version 0.5.5 (block#4788)
  feat(projects): support multiple repositories (block#4671)
  fix(ci): make desktop cache test version agnostic (block#4791)
  fix(desktop): widen post-Enter timeouts in empty-edit-delete spec (block#4792)
  fix(desktop): wait for terminal frame before splash (block#4781)
  fix(desktop): integer-align custom reaction emoji (block#4779)
  Polish Huddle voice controls (block#4694)
  fix(local-archive): default both archive settings to enabled (block#4750)
  fix(mobile): stop oversized read-state retry loop (block#4595)
  fix(desktop): close reconnect gaps that previously required CMD+R (block#4737)
  Dock Buzz Term within channel workspace (block#4724)
  perf(relay): index channel-id lookups and skip trace-only reads (block#4647)
  fix(agents): canonicalize stale persona harness pins (block#4631)
  Refine community invite links (block#4734)
  feat(desktop): persist sidebar observed-unread across webview reload (block#3976)
  feat(desktop): surface config diff in restart-required badge (block#3637)
  Polish sidebar unread hierarchy (block#4573)
  fix(desktop): show cached display names on startup (block#3317)
  ...

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants