diff --git a/ui/server/routes/config.js b/ui/server/routes/config.js index 3bec8565..452607e3 100644 --- a/ui/server/routes/config.js +++ b/ui/server/routes/config.js @@ -23,8 +23,16 @@ import { getPilotDeckGateway } from '../pilotdeck-bridge.js'; async function notifyGatewayConfigReload() { try { const gw = await getPilotDeckGateway(); - if (gw?.reloadConfig) await gw.reloadConfig(); - } catch { /* gateway unreachable — self-watch will pick up the change */ } + if (!gw?.reloadConfig) { + return { skipped: true, reason: 'gateway_reload_unavailable' }; + } + const result = await gw.reloadConfig(); + return { reloaded: result?.reloaded !== false, ...(result?.changedPaths ? { changedPaths: result.changedPaths } : {}) }; + } catch (error) { + return { + error: error instanceof Error ? error.message : String(error), + }; + } } const router = express.Router(); @@ -126,8 +134,10 @@ router.put('/', async (req, res) => { return res.status(400).json({ error: 'raw YAML or config object is required' }); } - const reloadResult = await reloadPilotDeckConfig(saved.config); - void notifyGatewayConfigReload(); + const reloadResult = { + ...(await reloadPilotDeckConfig(saved.config)), + gateway: await notifyGatewayConfigReload(), + }; // Re-read disk so the response's `raw` field comes from the actual // (lossless) file rather than the lossy round-trip output, and so // `serializeConfigResponse` has a `rawYaml` to render the full view. @@ -150,8 +160,10 @@ router.post('/reload', async (_req, res) => { if (!validation.valid) { return res.status(400).json({ error: 'Invalid config', validation }); } - const reloadResult = await reloadPilotDeckConfig(record.config); - void notifyGatewayConfigReload(); + const reloadResult = { + ...(await reloadPilotDeckConfig(record.config)), + gateway: await notifyGatewayConfigReload(), + }; const response = serializeConfigResponse(record, reloadResult); broadcastConfigEvent({ source: 'ui-reload', ...response, timestamp: new Date().toISOString() }); res.json(response); diff --git a/ui/src/hooks/usePilotDeckConfig.ts b/ui/src/hooks/usePilotDeckConfig.ts index 4b4590aa..46ee58ef 100644 --- a/ui/src/hooks/usePilotDeckConfig.ts +++ b/ui/src/hooks/usePilotDeckConfig.ts @@ -40,6 +40,16 @@ type ReloadInfo = { at: number; }; +function reloadErrorMessage(reload: ConfigReload | undefined, action: 'save' | 'reload'): string | null { + const gatewayError = reload?.gateway?.error; + if (gatewayError) { + return action === 'save' + ? `Saved config, but gateway reload failed: ${gatewayError}` + : `Gateway reload failed: ${gatewayError}`; + } + return null; +} + export function usePilotDeckConfig() { const [path, setPath] = useState(''); const [raw, setRaw] = useState(''); @@ -210,7 +220,12 @@ export function usePilotDeckConfig() { const data = await response.json(); if (!response.ok) throw new Error(data.error || data.validation?.errors?.join(', ') || 'Failed to save config'); applyResponse(data, 'ui-save'); - setMessage('Saved and reloaded'); + const reloadError = reloadErrorMessage(data.reload, 'save'); + if (reloadError) { + setError(reloadError); + } else { + setMessage('Saved and reloaded'); + } setExternalChangeNotice(null); } catch (caught) { setError(caught instanceof Error ? caught.message : 'Failed to save config'); @@ -228,7 +243,12 @@ export function usePilotDeckConfig() { const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Failed to reload config'); applyResponse(data, 'ui-reload'); - setMessage('Reloaded current config'); + const reloadError = reloadErrorMessage(data.reload, 'reload'); + if (reloadError) { + setError(reloadError); + } else { + setMessage('Reloaded current config'); + } } catch (caught) { setError(caught instanceof Error ? caught.message : 'Failed to reload config'); } finally {