Skip to content

Commit ad96fae

Browse files
committed
fix: preserve manual restart notice and add sidebar tooltip
1 parent c087b20 commit ad96fae

File tree

4 files changed

+91
-23
lines changed

4 files changed

+91
-23
lines changed

src-tauri/src/backend/app_server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ pub(crate) async fn opencode_restart_required_status() -> Value {
247247
"serverStartedAt": server_started_at.to_rfc3339(),
248248
"latestConfigChangeAt": latest_change.map(|dt| dt.to_rfc3339()),
249249
"reason": if required {
250-
Some("OpenCode config changed after the managed server started")
250+
Some("Config changed since server start")
251251
} else {
252252
None::<&str>
253253
}

src/features/app/components/SidebarCornerActions.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ export function SidebarCornerActions({
7171
return;
7272
}
7373
if (status.required) {
74-
markOpenCodeRestartRequired(status.reason ?? "OpenCode config changed.");
75-
} else {
76-
clearOpenCodeRestartRequired();
74+
markOpenCodeRestartRequired(status.reason ?? "OpenCode config changed.", "detector");
7775
}
7876
} catch {
7977
// No-op: banner is best-effort and should not break sidebar interactions.
@@ -106,25 +104,13 @@ export function SidebarCornerActions({
106104
}
107105
};
108106

109-
const restartTooltip = restartNotice.reason
110-
? `OpenCode config changes are pending. ${restartNotice.reason} Restart the OpenCode server to apply updated agents and models.`
111-
: "OpenCode config changes are pending. Restart the OpenCode server to apply updated agents and models.";
107+
const reasonText = restartNotice.reason?.trim().replace(/[.\s]+$/, "") ?? "";
108+
const restartTooltip = reasonText
109+
? `${reasonText}. Restart OpenCode to refresh agents and models.`
110+
: "Config changed. Restart OpenCode to refresh agents and models.";
112111

113112
return (
114113
<div className="sidebar-corner-actions">
115-
{restartNotice.required && (
116-
<button
117-
type="button"
118-
className={`sidebar-restart-banner${restartingServer ? " is-restarting" : ""}`}
119-
onClick={() => void handleRestartServer()}
120-
disabled={restartingServer}
121-
aria-label="Restart OpenCode server to apply config changes"
122-
title={restartTooltip}
123-
>
124-
<RotateCcw size={12} aria-hidden />
125-
<span>{restartingServer ? "Restarting..." : "Restart OpenCode"}</span>
126-
</button>
127-
)}
128114
<div className="sidebar-corner-actions-row">
129115
<button
130116
className="ghost sidebar-corner-button"
@@ -135,6 +121,29 @@ export function SidebarCornerActions({
135121
>
136122
<Settings size={14} aria-hidden />
137123
</button>
124+
{restartNotice.required && (
125+
<div className="sidebar-restart-banner-wrap">
126+
<button
127+
type="button"
128+
className={`sidebar-restart-banner${restartingServer ? " is-restarting" : ""}`}
129+
onClick={() => void handleRestartServer()}
130+
disabled={restartingServer}
131+
aria-label="Restart OpenCode server to apply config changes"
132+
aria-describedby="sidebar-restart-tooltip"
133+
>
134+
<RotateCcw size={12} aria-hidden />
135+
<span>{restartingServer ? "Restarting..." : "Config changed"}</span>
136+
</button>
137+
<div
138+
id="sidebar-restart-tooltip"
139+
role="tooltip"
140+
className="sidebar-restart-tooltip"
141+
>
142+
<strong>Restart required</strong>
143+
<span>{restartTooltip}</span>
144+
</div>
145+
</div>
146+
)}
138147
{showDebugButton && (
139148
<button
140149
className="ghost sidebar-corner-button"

src/services/opencodeRestartNotice.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ export type OpenCodeRestartNotice = {
66
required: boolean;
77
updatedAt: number | null;
88
reason: string | null;
9+
source: "manual" | "detector" | null;
910
};
1011

1112
const DEFAULT_NOTICE: OpenCodeRestartNotice = {
1213
required: false,
1314
updatedAt: null,
1415
reason: null,
16+
source: null,
1517
};
1618

1719
function canUseBrowserStorage() {
@@ -39,20 +41,26 @@ export function readOpenCodeRestartNotice(): OpenCodeRestartNotice {
3941
required: parsed.required === true,
4042
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : null,
4143
reason: typeof parsed.reason === "string" ? parsed.reason : null,
44+
source:
45+
parsed.source === "manual" || parsed.source === "detector" ? parsed.source : null,
4246
};
4347
} catch {
4448
return DEFAULT_NOTICE;
4549
}
4650
}
4751

48-
export function markOpenCodeRestartRequired(reason?: string | null) {
52+
export function markOpenCodeRestartRequired(
53+
reason?: string | null,
54+
source: "manual" | "detector" = "manual",
55+
) {
4956
if (!canUseBrowserStorage()) {
5057
return;
5158
}
5259
const next: OpenCodeRestartNotice = {
5360
required: true,
5461
updatedAt: Date.now(),
5562
reason: reason?.trim() ? reason.trim() : null,
63+
source,
5664
};
5765
try {
5866
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));

src/styles/sidebar.css

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,8 +1325,7 @@
13251325
left: var(--sidebar-padding);
13261326
bottom: 16px;
13271327
display: inline-flex;
1328-
flex-direction: column;
1329-
align-items: flex-start;
1328+
align-items: center;
13301329
gap: 8px;
13311330
}
13321331

@@ -1336,6 +1335,12 @@
13361335
gap: 8px;
13371336
}
13381337

1338+
.sidebar-restart-banner-wrap {
1339+
position: relative;
1340+
display: inline-flex;
1341+
align-items: center;
1342+
}
1343+
13391344
.sidebar-restart-banner {
13401345
display: inline-flex;
13411346
align-items: center;
@@ -1374,6 +1379,52 @@
13741379
animation: sidebar-account-spin 0.8s linear infinite;
13751380
}
13761381

1382+
.sidebar-restart-tooltip {
1383+
position: absolute;
1384+
right: 0;
1385+
bottom: calc(100% + 10px);
1386+
width: min(248px, calc(var(--sidebar-width, 280px) - (var(--sidebar-padding) * 2)));
1387+
min-width: 0;
1388+
display: grid;
1389+
gap: 4px;
1390+
padding: 8px 10px;
1391+
border-radius: 10px;
1392+
border: 1px solid rgba(255, 255, 255, 0.1);
1393+
background: rgba(10, 13, 18, 0.96);
1394+
color: rgba(235, 240, 248, 0.95);
1395+
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35);
1396+
font-size: 11px;
1397+
line-height: 1.35;
1398+
white-space: normal;
1399+
overflow-wrap: break-word;
1400+
opacity: 0;
1401+
transform: translateY(4px);
1402+
pointer-events: none;
1403+
transition: opacity 120ms ease, transform 120ms ease;
1404+
z-index: 20;
1405+
}
1406+
1407+
.sidebar-restart-tooltip strong {
1408+
font-size: 11px;
1409+
font-weight: 700;
1410+
color: rgba(255, 228, 181, 0.98);
1411+
}
1412+
1413+
.sidebar-restart-banner-wrap:hover .sidebar-restart-tooltip,
1414+
.sidebar-restart-banner-wrap:focus-within .sidebar-restart-tooltip {
1415+
opacity: 1;
1416+
transform: translateY(0);
1417+
}
1418+
1419+
.sidebar-restart-tooltip::after {
1420+
content: "";
1421+
position: absolute;
1422+
right: 18px;
1423+
top: 100%;
1424+
border: 6px solid transparent;
1425+
border-top-color: rgba(10, 13, 18, 0.96);
1426+
}
1427+
13771428
.sidebar-corner-button {
13781429
padding: 6px;
13791430
border-radius: 8px;

0 commit comments

Comments
 (0)