From 434c72757c4d05f6793b10ef014a483401b71c8f Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Tue, 25 Aug 2026 12:06:48 -0700 Subject: [PATCH] Persist shared instruction restart intent Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho --- desktop/src/features/agents/AGENTS.md | 8 +++- .../settings/ui/ExperimentalFeaturesCard.tsx | 17 +++++++- .../sharedInstructionsRestartPending.test.mjs | 39 +++++++++++++++++++ .../ui/sharedInstructionsRestartPending.ts | 31 +++++++++++++++ 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 desktop/src/features/settings/ui/sharedInstructionsRestartPending.test.mjs create mode 100644 desktop/src/features/settings/ui/sharedInstructionsRestartPending.ts diff --git a/desktop/src/features/agents/AGENTS.md b/desktop/src/features/agents/AGENTS.md index 470b59bf09..91a6c3d3c5 100644 --- a/desktop/src/features/agents/AGENTS.md +++ b/desktop/src/features/agents/AGENTS.md @@ -192,7 +192,13 @@ with a TypeScript lookup table or an id comparison in a component. its assignment. Dirty Title, When to use it, or Instructions fields must confirm before Cancel or the enclosing dialog close discards them. The primary create action is **Publish**, and the footer discloses that anyone - in the community can read the instruction. + in the community can read the instruction. The `sharedInstructions` + experiment gates execution, not only this UI: local prospective restart + snapshots and remote deployment payloads must both omit assignments while + disabled. A successful toggle records the running value until the user + restarts agents (or toggles back), so the restart affordance survives + leaving and reopening Settings without claiming a restart is needed when + runtime context is unchanged. 14. **Profile runtime sections render only reported agent data.** Missing runtime, model, status, command, MCP, advanced, or diagnostics values stay absent in every build mode. Do not fill profile or agent-panel gaps with diff --git a/desktop/src/features/settings/ui/ExperimentalFeaturesCard.tsx b/desktop/src/features/settings/ui/ExperimentalFeaturesCard.tsx index e342c5bd31..6b57d4164e 100644 --- a/desktop/src/features/settings/ui/ExperimentalFeaturesCard.tsx +++ b/desktop/src/features/settings/ui/ExperimentalFeaturesCard.tsx @@ -15,10 +15,19 @@ import { Button } from "@/shared/ui/button"; import { Switch } from "@/shared/ui/switch"; import { SettingsOptionGroup, SettingsOptionRow } from "./SettingsOptionGroup"; import { SettingsSectionHeader } from "./SettingsSectionHeader"; +import { + clearSharedInstructionsRestartPending, + isSharedInstructionsRestartPending, + recordSharedInstructionsToggle, +} from "./sharedInstructionsRestartPending"; function FeatureRow({ feature }: { feature: FeatureDefinition }) { const [enabled, toggle] = useFeatureToggle(feature.id); - const [restartPending, setRestartPending] = React.useState(false); + const [restartPending, setRestartPending] = React.useState(() => + feature.id === "sharedInstructions" + ? isSharedInstructionsRestartPending(enabled) + : false, + ); const [restarting, setRestarting] = React.useState(false); const switchId = `feature-toggle-${feature.id}`; @@ -40,6 +49,7 @@ function FeatureRow({ feature }: { feature: FeatureDefinition }) { ); return; } + clearSharedInstructionsRestartPending(); setRestartPending(false); toast.success( running.length === 0 @@ -99,7 +109,10 @@ function FeatureRow({ feature }: { feature: FeatureDefinition }) { } if (feature.id === "sharedInstructions") { void setSharedInstructionsEnabled(value) - .then(() => setRestartPending(true)) + .then(() => { + const pending = recordSharedInstructionsToggle(value); + setRestartPending(pending); + }) .catch((error) => { console.error( "Failed to apply shared instructions setting:", diff --git a/desktop/src/features/settings/ui/sharedInstructionsRestartPending.test.mjs b/desktop/src/features/settings/ui/sharedInstructionsRestartPending.test.mjs new file mode 100644 index 0000000000..a5104ef5e9 --- /dev/null +++ b/desktop/src/features/settings/ui/sharedInstructionsRestartPending.test.mjs @@ -0,0 +1,39 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +const values = new Map(); +globalThis.window = { + sessionStorage: { + getItem(key) { + return values.get(key) ?? null; + }, + removeItem(key) { + values.delete(key); + }, + setItem(key, value) { + values.set(key, value); + }, + }, +}; + +const { + clearSharedInstructionsRestartPending, + isSharedInstructionsRestartPending, + recordSharedInstructionsToggle, +} = await import("./sharedInstructionsRestartPending.ts"); + +test.beforeEach(() => values.clear()); + +test("pending state survives remount until restart", () => { + assert.equal(recordSharedInstructionsToggle(true), true); + assert.equal(isSharedInstructionsRestartPending(true), true); + + clearSharedInstructionsRestartPending(); + assert.equal(isSharedInstructionsRestartPending(true), false); +}); + +test("toggling back to the running value clears pending state", () => { + assert.equal(recordSharedInstructionsToggle(true), true); + assert.equal(recordSharedInstructionsToggle(false), false); + assert.equal(isSharedInstructionsRestartPending(false), false); +}); diff --git a/desktop/src/features/settings/ui/sharedInstructionsRestartPending.ts b/desktop/src/features/settings/ui/sharedInstructionsRestartPending.ts new file mode 100644 index 0000000000..97f4898b64 --- /dev/null +++ b/desktop/src/features/settings/ui/sharedInstructionsRestartPending.ts @@ -0,0 +1,31 @@ +export const SHARED_INSTRUCTIONS_RESTART_BASELINE_KEY = + "buzz.sharedInstructionsRestartBaseline"; + +function storage(): Storage | null { + return typeof window === "undefined" ? null : window.sessionStorage; +} + +export function isSharedInstructionsRestartPending(current: boolean): boolean { + const baseline = storage()?.getItem(SHARED_INSTRUCTIONS_RESTART_BASELINE_KEY); + return baseline !== null && baseline !== String(current); +} + +export function recordSharedInstructionsToggle(current: boolean): boolean { + const store = storage(); + if (!store) return false; + + const baseline = store.getItem(SHARED_INSTRUCTIONS_RESTART_BASELINE_KEY); + if (baseline === null) { + store.setItem(SHARED_INSTRUCTIONS_RESTART_BASELINE_KEY, String(!current)); + return true; + } + if (baseline === String(current)) { + store.removeItem(SHARED_INSTRUCTIONS_RESTART_BASELINE_KEY); + return false; + } + return true; +} + +export function clearSharedInstructionsRestartPending(): void { + storage()?.removeItem(SHARED_INSTRUCTIONS_RESTART_BASELINE_KEY); +}