[eric] apps: auto hard-reload the app card when a turn installed deps (soft reload can't see new packages)

This commit is contained in:
ciregenz
2026-07-07 01:31:35 -07:00
parent 08d5bd3cc6
commit 953fddc99e
5 changed files with 81 additions and 1 deletions
@@ -179,10 +179,30 @@ const DashboardViewCard: React.FC<Props> = ({
const [finishing, setFinishing] = useState(false);
const wasBuildingRef = useRef(false);
const finishTimerRef = useRef<number | null>(null);
// Whether this turn changed deps (needs a Vite restart, not just a soft reload). Held in a ref so the reload effect stays keyed on the status transition alone.
const depsChanged = useAppSelector(
(s) => (output.session_id ? !!s.agents.sessions[output.session_id]?.app_deps_changed : false),
);
const depsChangedRef = useRef(false);
useEffect(() => { depsChangedRef.current = depsChanged; }, [depsChanged]);
useEffect(() => {
const building = linkedStatus === 'running' || linkedStatus === 'waiting_approval';
if (wasBuildingRef.current && !building) {
previewRef.current?.reload();
const wsId = output.workspace_id;
if (depsChangedRef.current && wsId) {
// Deps changed this turn: a soft reload can't pick up new packages, so restart the Vite runtime first, then reload.
void (async () => {
try {
const tok = getAuthToken();
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (tok) headers.Authorization = `Bearer ${tok}`;
await fetch(`${API_BASE}/outputs/workspace/${wsId}/runtime/restart?instance=${instance}`, { method: 'POST', headers });
} catch { /* failures surface via the runtime log WS */ }
previewRef.current?.reload();
})();
} else {
previewRef.current?.reload();
}
setFinishing(true);
if (finishTimerRef.current) clearTimeout(finishTimerRef.current);
finishTimerRef.current = window.setTimeout(() => setFinishing(false), 1200);
+12
View File
@@ -110,6 +110,8 @@ export interface AgentSession {
context_overflow?: { reason: string; message: string; at: string } | null;
rate_limited?: { retry_after_s: number | null; at: string } | null;
context_recovered?: { at: string } | null;
// Set when a view-builder turn installed/changed deps, so the app card does a HARD reload (Vite restart) at turn-finish instead of the soft one. Reset when the next turn starts.
app_deps_changed?: boolean;
mcp_suggestions?: Array<{ id: string; title: string; description: string; reason?: string }>;
mcp_suggestions_is_vague?: boolean;
compacted_through_msg_id?: string | null;
@@ -721,6 +723,10 @@ const agentsSlice = createSlice({
if (terminal.includes(session.status as any) && action.payload.status === 'running') {
return;
}
// A fresh turn clears last turn's deps-changed flag so the app card only hard-reloads for the turn that actually changed deps.
if (action.payload.status === 'running' && session.status !== 'running') {
session.app_deps_changed = false;
}
session.status = action.payload.status;
}
if (action.payload.status === 'running' && !state.trackedNotificationIds.includes(action.payload.sessionId)) {
@@ -960,6 +966,11 @@ const agentsSlice = createSlice({
if (session) session.rate_limited = null;
},
setAppDepsChanged(state, action: PayloadAction<{ sessionId: string }>) {
const session = state.sessions[action.payload.sessionId];
if (session) session.app_deps_changed = true;
},
setContextRecovered(state, action: PayloadAction<{ sessionId: string }>) {
const session = state.sessions[action.payload.sessionId];
if (session) session.context_recovered = { at: new Date().toISOString() };
@@ -1453,6 +1464,7 @@ export const {
clearRateLimited,
setContextRecovered,
clearContextRecovered,
setAppDepsChanged,
clearContextOverflow,
setMcpSuggestions,
clearMcpSuggestions,
@@ -14,6 +14,7 @@ import {
setContextOverflow,
setRateLimited,
setContextRecovered,
setAppDepsChanged,
setMcpSuggestions,
addBranch,
setActiveBranch,
@@ -562,6 +563,13 @@ class WebSocketManager {
}
break;
case 'agent:app_deps_changed':
// A view-builder agent installed/changed deps this turn; the app card escalates its turn-finish reload from soft to a Vite restart so new deps actually load.
if (session_id) {
store.dispatch(setAppDepsChanged({ sessionId: session_id }));
}
break;
case 'agent:context_status':
// Auto-compaction collapsed older turns into a summary. Mirror compacted_through_msg_id locally so the renderer can drop a visible "N earlier turns summarized" chip into the transcript. Other reasons (cleared, etc.) flow through this same event but don't currently need a chip, ignore them for now.
if (session_id && data.reason === 'compacted') {