Skip to content

Commit fb7557c

Browse files
committed
fix(desktop): keep Settings scoped during Host refresh
Preserve the selected Host snapshot while its settings and connections refresh, so background invalidations cannot discard in-progress edits. Route Host-backed diagnostics, imports, and configuration through that same target instead of silently falling back to the default Host. Generated-by: OpenAI Codex
1 parent ade6bd8 commit fb7557c

8 files changed

Lines changed: 135 additions & 81 deletions

File tree

‎apps/desktop/src/preload/bridge-contract.d.ts‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -627,15 +627,15 @@ export interface MakaBridge {
627627
): Promise<DesktopTranscriptHandle>;
628628
};
629629
externalSessions: {
630-
listSources(): Promise<{ adapterIds: string[] }>;
631-
list(input: { adapterId: string; includeArchived?: boolean; cursor?: string }): Promise<{
630+
listSources(host?: DesktopRuntimeHostRef): Promise<{ adapterIds: string[] }>;
631+
list(input: { adapterId: string; includeArchived?: boolean; cursor?: string }, host?: DesktopRuntimeHostRef): Promise<{
632632
sessions: ExternalSessionSummary[];
633633
nextCursor: string | null;
634634
}>;
635635
import(input: {
636636
adapterId: string;
637637
sourceSessionId: string;
638-
}): Promise<ExternalSessionImportIpcResult<DesktopSessionSummary>>;
638+
}, host?: DesktopRuntimeHostRef): Promise<ExternalSessionImportIpcResult<DesktopSessionSummary>>;
639639
};
640640
projects: {
641641
getDefaultContext(): Promise<{
@@ -785,22 +785,22 @@ export interface MakaBridge {
785785
): Promise<import('@maka/core/task-submission-readiness').TaskSubmissionReadinessSnapshot>;
786786
};
787787
permissions: {
788-
getSnapshot(): Promise<PermissionSnapshot>;
789-
openSystemSettings(permId: string): Promise<PermissionActionResult>;
790-
requestAccess(permId: string): Promise<PermissionActionResult>;
788+
getSnapshot(host?: DesktopRuntimeHostRef): Promise<PermissionSnapshot>;
789+
openSystemSettings(permId: string, host?: DesktopRuntimeHostRef): Promise<PermissionActionResult>;
790+
requestAccess(permId: string, host?: DesktopRuntimeHostRef): Promise<PermissionActionResult>;
791791
/**
792792
* macOS drag-to-grant onboarding: opens the right Privacy pane and
793793
* floats a card the user can drag the app bundle out of. Only
794794
* `accessibility` and `screen_recording` — the two permissions with
795795
* no programmatic consent dialog.
796796
*/
797-
startDragOnboarding(permId: string): Promise<PermissionOverlayStartResult>;
797+
startDragOnboarding(permId: string, host?: DesktopRuntimeHostRef): Promise<PermissionOverlayStartResult>;
798798
};
799799
capabilities: {
800-
getSnapshot(): Promise<CapabilitySnapshotCollection>;
800+
getSnapshot(host?: DesktopRuntimeHostRef): Promise<CapabilitySnapshotCollection>;
801801
};
802802
health: {
803-
getSnapshot(): Promise<HealthSnapshot>;
803+
getSnapshot(host?: DesktopRuntimeHostRef): Promise<HealthSnapshot>;
804804
};
805805
memory: {
806806
getState(sessionId?: string, host?: DesktopRuntimeHostRef): Promise<LocalMemoryState>;
@@ -985,11 +985,11 @@ export interface MakaBridge {
985985
subscribeCommand(handler: (command: WindowCommand) => void): () => void;
986986
};
987987
config: {
988-
export(input: { categories: ConfigCategory[] }): Promise<
988+
export(input: { categories: ConfigCategory[] }, host?: DesktopRuntimeHostRef): Promise<
989989
| { ok: false; reason: 'no_categories' | 'canceled' }
990990
| { ok: true; path: string; includedData: ConfigCategory[] }
991991
>;
992-
import(input: { strategy: 'skip' | 'overwrite' }): Promise<
992+
import(input: { strategy: 'skip' | 'overwrite' }, host?: DesktopRuntimeHostRef): Promise<
993993
| { ok: false; reason: 'canceled' | 'not_json' | 'malformed' | 'unsupported_version'; message?: string }
994994
| {
995995
ok: true;
@@ -1008,7 +1008,7 @@ export interface MakaBridge {
10081008
>;
10091009
};
10101010
app: {
1011-
info(): Promise<DesktopAppInfo>;
1011+
info(host?: DesktopRuntimeHostRef): Promise<DesktopAppInfo>;
10121012
subscribeUpdateStatus(handler: (status: AppUpdateStatus) => void): () => void;
10131013
updateStatus(): Promise<AppUpdateStatus>;
10141014
checkForUpdates(): Promise<AppUpdateStatus>;
@@ -1021,6 +1021,7 @@ export interface MakaBridge {
10211021
openPath(
10221022
key: 'workspace' | 'skills' | 'memory' | 'project',
10231023
sessionId?: string,
1024+
host?: DesktopRuntimeHostRef,
10241025
): Promise<
10251026
| { ok: true; opened: string }
10261027
| {

‎apps/desktop/src/preload/preload.ts‎

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,21 +1672,24 @@ const makaBridge = {
16721672
},
16731673
},
16741674
externalSessions: {
1675-
listSources(): Promise<{ adapterIds: string[] }> {
1676-
return invokeActiveRuntimeHost('external-sessions:listSources');
1675+
listSources(host?: DesktopRuntimeHostRef): Promise<{ adapterIds: string[] }> {
1676+
return invokeSelectedRuntimeHost(host, 'external-sessions:listSources');
16771677
},
16781678
list(input: {
16791679
adapterId: string;
16801680
includeArchived?: boolean;
16811681
cursor?: string;
1682-
}): Promise<{ sessions: ExternalSessionSummary[]; nextCursor: string | null }> {
1683-
return invokeActiveRuntimeHost('external-sessions:list', input);
1682+
}, host?: DesktopRuntimeHostRef): Promise<{
1683+
sessions: ExternalSessionSummary[];
1684+
nextCursor: string | null;
1685+
}> {
1686+
return invokeSelectedRuntimeHost(host, 'external-sessions:list', input);
16841687
},
16851688
async import(input: {
16861689
adapterId: string;
16871690
sourceSessionId: string;
1688-
}): Promise<ExternalSessionImportIpcResult<DesktopSessionSummary>> {
1689-
const scope = await activeRuntimeHostRef();
1691+
}, host?: DesktopRuntimeHostRef): Promise<ExternalSessionImportIpcResult<DesktopSessionSummary>> {
1692+
const scope = await selectedRuntimeHostScope(host);
16901693
const result = await ipcRenderer.invoke(
16911694
'external-sessions:import', scope, input,
16921695
) as ExternalSessionImportIpcResult;
@@ -1994,27 +1997,27 @@ const makaBridge = {
19941997
},
19951998
},
19961999
permissions: {
1997-
getSnapshot(): Promise<PermissionSnapshot> {
1998-
return invokeActiveRuntimeHost('permissions:getSnapshot');
2000+
getSnapshot(host?: DesktopRuntimeHostRef): Promise<PermissionSnapshot> {
2001+
return invokeSelectedRuntimeHost(host, 'permissions:getSnapshot');
19992002
},
2000-
openSystemSettings(permId: string): Promise<PermissionActionResult> {
2001-
return invokeActiveRuntimeHost('permissions:openSystemSettings', permId);
2003+
openSystemSettings(permId: string, host?: DesktopRuntimeHostRef): Promise<PermissionActionResult> {
2004+
return invokeSelectedRuntimeHost(host, 'permissions:openSystemSettings', permId);
20022005
},
2003-
requestAccess(permId: string): Promise<PermissionActionResult> {
2004-
return invokeActiveRuntimeHost('permissions:requestAccess', permId);
2006+
requestAccess(permId: string, host?: DesktopRuntimeHostRef): Promise<PermissionActionResult> {
2007+
return invokeSelectedRuntimeHost(host, 'permissions:requestAccess', permId);
20052008
},
2006-
startDragOnboarding(permId: string): Promise<PermissionOverlayStartResult> {
2007-
return invokeActiveRuntimeHost('permissions:startDragOnboarding', permId);
2009+
startDragOnboarding(permId: string, host?: DesktopRuntimeHostRef): Promise<PermissionOverlayStartResult> {
2010+
return invokeSelectedRuntimeHost(host, 'permissions:startDragOnboarding', permId);
20082011
},
20092012
},
20102013
capabilities: {
2011-
getSnapshot(): Promise<CapabilitySnapshotCollection> {
2012-
return invokeActiveRuntimeHost('capabilities:getSnapshot');
2014+
getSnapshot(host?: DesktopRuntimeHostRef): Promise<CapabilitySnapshotCollection> {
2015+
return invokeSelectedRuntimeHost(host, 'capabilities:getSnapshot');
20132016
},
20142017
},
20152018
health: {
2016-
getSnapshot(): Promise<HealthSnapshot> {
2017-
return invokeActiveRuntimeHost('health:getSnapshot');
2019+
getSnapshot(host?: DesktopRuntimeHostRef): Promise<HealthSnapshot> {
2020+
return invokeSelectedRuntimeHost(host, 'health:getSnapshot');
20182021
},
20192022
},
20202023
memory: {
@@ -2516,13 +2519,13 @@ const makaBridge = {
25162519
},
25172520
},
25182521
config: {
2519-
export(input: { categories: ConfigCategory[] }): Promise<
2522+
export(input: { categories: ConfigCategory[] }, host?: DesktopRuntimeHostRef): Promise<
25202523
| { ok: false; reason: 'no_categories' | 'canceled' }
25212524
| { ok: true; path: string; includedData: ConfigCategory[] }
25222525
> {
2523-
return invokeActiveRuntimeHost('config:export', input);
2526+
return invokeSelectedRuntimeHost(host, 'config:export', input);
25242527
},
2525-
import(input: { strategy: 'skip' | 'overwrite' }): Promise<
2528+
import(input: { strategy: 'skip' | 'overwrite' }, host?: DesktopRuntimeHostRef): Promise<
25262529
| { ok: false; reason: 'canceled' | 'not_json' | 'malformed' | 'unsupported_version'; message?: string }
25272530
| {
25282531
ok: true;
@@ -2539,12 +2542,12 @@ const makaBridge = {
25392542
};
25402543
}
25412544
> {
2542-
return invokeActiveRuntimeHost('config:import', input);
2545+
return invokeSelectedRuntimeHost(host, 'config:import', input);
25432546
},
25442547
},
25452548
app: {
2546-
info(): Promise<DesktopAppInfo> {
2547-
return invokeActiveRuntimeHost('app:info');
2549+
info(host?: DesktopRuntimeHostRef): Promise<DesktopAppInfo> {
2550+
return invokeSelectedRuntimeHost(host, 'app:info');
25482551
},
25492552
subscribeUpdateStatus(handler: (status: AppUpdateStatus) => void): () => void {
25502553
const listener = (_event: Electron.IpcRendererEvent, status: AppUpdateStatus) => handler(status);
@@ -2572,14 +2575,15 @@ const makaBridge = {
25722575
openPath(
25732576
key: 'workspace' | 'skills' | 'memory' | 'project',
25742577
sessionId?: string,
2578+
host?: DesktopRuntimeHostRef,
25752579
): Promise<
25762580
| { ok: true; opened: string }
25772581
| {
25782582
ok: false;
25792583
reason: 'unknown-key' | 'not-allowed' | 'missing' | 'not-a-directory' | 'open-failed';
25802584
}
25812585
> {
2582-
if (!sessionId) return invokeActiveRuntimeHost('app:openPath', key, undefined);
2586+
if (!sessionId) return invokeSelectedRuntimeHost(host, 'app:openPath', key, undefined);
25832587
return runtimeHostSessionRef(sessionId).then((session) =>
25842588
ipcRenderer.invoke('app:openPath', session.scope, key, session.sessionId),
25852589
);

‎apps/desktop/src/renderer/settings/data-settings-page.tsx‎

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { settingsActionErrorMessage } from './settings-error-copy';
1717
import { useActionGuard } from './use-action-guard';
1818
import { getDataSettingsCopy, type DataSettingsCopy } from '../locales/settings-data-copy';
1919
import { getSettingsSharedCopy } from '../locales/settings-shared-copy.js';
20+
import { useOptionalRuntimeHostSettingsTarget } from './runtime-host-settings-target.js';
2021

2122
const CONFIG_CATEGORY_IDS: readonly ConfigCategory[] = ['connections', 'settings', 'memory', 'credentials'];
2223

@@ -35,7 +36,11 @@ function summarizeImportResult(result: ConfigImportResult, copy: DataSettingsCop
3536
return parts.join(' · ') || copy.importSummary.empty;
3637
}
3738

38-
export function DataSettingsPage() {
39+
export function DataSettingsPage(props: {
40+
runtimeHostStatus: 'loading' | 'ready' | 'unavailable' | 'error';
41+
onRetryRuntimeHost(): Promise<void>;
42+
}) {
43+
const host = useOptionalRuntimeHostSettingsTarget();
3944
const locale = useUiLocale();
4045
const copy = getDataSettingsCopy(locale);
4146
const sharedCopy = getSettingsSharedCopy(locale);
@@ -50,10 +55,16 @@ export function DataSettingsPage() {
5055
);
5156
const [importStrategy, setImportStrategy] = useState<'skip' | 'overwrite'>('skip');
5257
const [configBusy, setConfigBusy] = useState<null | 'export' | 'import'>(null);
58+
const runtimeHostAvailable = host !== undefined && props.runtimeHostStatus === 'ready';
5359

5460
useEffect(() => {
61+
if (!host) {
62+
setInfo(null);
63+
setInfoError(null);
64+
return;
65+
}
5566
let cancelled = false;
56-
void window.maka.app.info().then((next) => {
67+
void window.maka.app.info(host).then((next) => {
5768
if (!cancelled) {
5869
setInfo(next);
5970
setInfoError(null);
@@ -68,7 +79,7 @@ export function DataSettingsPage() {
6879
return () => {
6980
cancelled = true;
7081
};
71-
}, [locale, toast]);
82+
}, [host, locale, toast]);
7283

7384
async function runDataAction(action: string, run: () => Promise<void>) {
7485
if (!dataActionGuard.begin(action)) return;
@@ -90,7 +101,7 @@ export function DataSettingsPage() {
90101
if (!info) return;
91102
await runDataAction('workspace:open', async () => {
92103
try {
93-
const result = await window.maka.app.openPath('workspace');
104+
const result = await window.maka.app.openPath('workspace', undefined, host);
94105
if (!dataPageMountedRef.current) return;
95106
if (!result.ok) {
96107
toast.error(
@@ -141,15 +152,15 @@ export function DataSettingsPage() {
141152
}
142153

143154
async function exportConfig() {
144-
if (configBusy) return;
155+
if (configBusy || !host) return;
145156
const categories = [...selectedCategories];
146157
if (categories.length === 0) {
147158
toast.error(copy.selectCategory);
148159
return;
149160
}
150161
setConfigBusy('export');
151162
try {
152-
const res = await window.maka.config.export({ categories });
163+
const res = await window.maka.config.export({ categories }, host);
153164
if (res.ok) {
154165
toast.success(copy.exported, copy.exportedDetail(res.includedData));
155166
} else if (res.reason !== 'canceled') {
@@ -163,10 +174,10 @@ export function DataSettingsPage() {
163174
}
164175

165176
async function importConfig() {
166-
if (configBusy) return;
177+
if (configBusy || !host) return;
167178
setConfigBusy('import');
168179
try {
169-
const res = await window.maka.config.import({ strategy: importStrategy });
180+
const res = await window.maka.config.import({ strategy: importStrategy }, host);
170181
if (res.ok) {
171182
toast.success(copy.imported, summarizeImportResult(res.result, copy));
172183
} else if (res.reason !== 'canceled') {
@@ -184,6 +195,22 @@ export function DataSettingsPage() {
184195

185196
return (
186197
<SettingsPage>
198+
{!runtimeHostAvailable ? (
199+
<Banner
200+
status={props.runtimeHostStatus === 'error' ? 'error' : 'warning'}
201+
title={props.runtimeHostStatus === 'loading'
202+
? sharedCopy.loading
203+
: sharedCopy.runtimeHostUnavailable}
204+
endContent={props.runtimeHostStatus === 'error' ? (
205+
<Button
206+
variant="secondary"
207+
size="sm"
208+
label={sharedCopy.retry}
209+
onClick={() => void props.onRetryRuntimeHost()}
210+
/>
211+
) : undefined}
212+
/>
213+
) : null}
187214
<SettingsSection
188215
title={sharedCopy.groups.dataLocation}
189216
description={sharedCopy.groups.dataLocationHelp}
@@ -251,7 +278,7 @@ export function DataSettingsPage() {
251278
other group now; the Switch's own label/description layout IS the
252279
row, so each one is a SettingsField (padded, divided) rather than
253280
a re-labeled SettingsRow. */}
254-
<SettingsSection
281+
{runtimeHostAvailable ? <SettingsSection
255282
title={copy.configTitle}
256283
description={copy.configHelp}
257284
>
@@ -306,7 +333,7 @@ export function DataSettingsPage() {
306333
secondary. Two filled buttons recommended neither. */}
307334
<Button variant="secondary" isDisabled={configBusy !== null} clickAction={() => importConfig()} label={copy.importConfig} />
308335
</SettingsActions>
309-
</SettingsSection>
336+
</SettingsSection> : null}
310337
</SettingsPage>
311338
);
312339
}

‎apps/desktop/src/renderer/settings/health-center-page.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { settingsActionErrorMessage } from './settings-error-copy';
1212
import { SettingsPage, SettingsRow, SettingsSection } from './settings-section';
1313
import { SettingsSkeletonStack } from './settings-skeleton';
1414
import { dotForStatus } from '@maka/ui';
15+
import { useRuntimeHostSettingsTarget } from './runtime-host-settings-target.js';
1516

1617
/**
1718
* PR-UI-9 — Health Center read-only page. Consumes `window.maka.health.getSnapshot()`
@@ -29,6 +30,7 @@ import { dotForStatus } from '@maka/ui';
2930
* will be wired in PR-HC-2 once typed actions are exposed.
3031
*/
3132
export function HealthCenterPage() {
33+
const host = useRuntimeHostSettingsTarget();
3234
const locale = useUiLocale();
3335
const copy = getHealthCenterCopy(locale);
3436
const [snapshot, setSnapshot] = useState<HealthSnapshot | null>(null);
@@ -41,7 +43,7 @@ export function HealthCenterPage() {
4143
setLoading(true);
4244
setError(null);
4345
window.maka.health
44-
.getSnapshot()
46+
.getSnapshot(host)
4547
.then((next) => {
4648
if (cancelled) return;
4749
setSnapshot(next);
@@ -55,7 +57,7 @@ export function HealthCenterPage() {
5557
return () => {
5658
cancelled = true;
5759
};
58-
}, [locale, refreshTick]);
60+
}, [host, locale, refreshTick]);
5961

6062
if (loading) {
6163
return (

0 commit comments

Comments
 (0)