Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/desktop-instance-label.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inkeep/open-knowledge-desktop": patch
---

Parallel desktop instances now carry their instance name in the macOS menu-bar app name (`OpenKnowledge (work)`) and editor window titles, so multiple instances running side by side are distinguishable. The label is derived from the launch's per-instance `userData` directory — set by the parallel-instance launcher (`--user-data-dir ~/.ok/instances/<name>`) or dev `OK_INSTANCE` — so it requires no extra flags and is a no-op for the default install.
8 changes: 8 additions & 0 deletions packages/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import { EMBED_HOST_PATTERNS, rewriteEmbedRequestHeaders } from './embed-referer
import { discoverProject, validateFolderPick } from './folder-admission.ts';
import { ensureGitAvailable } from './git-preflight-handler.ts';
import { readCanonicalGitHubRemoteUrl } from './git-remote.ts';
import { formatInstanceAppName, resolveInstanceLabel } from './instance-identity.ts';
import { deriveInstanceUserDataDir } from './instance-isolation.ts';
import { handleBuildAndOpen, handleDetectClaudeDesktop } from './ipc/install-skill.ts';
import {
Expand Down Expand Up @@ -251,6 +252,7 @@ import { buildUtilityForkEnv } from './utility-fork-env.ts';
import { mergeViewMenuState } from './view-menu-state.ts';
import {
type BrowserWindowLike,
setWindowInstanceLabel,
type UtilityProcessLike,
WindowManager,
} from './window-manager.ts';
Expand Down Expand Up @@ -2457,6 +2459,12 @@ if (!app.isPackaged && process.env.OK_INSTANCE) {
}
}

const instanceLabel = resolveInstanceLabel(app.getPath('userData'));
if (instanceLabel) {
app.setName(formatInstanceAppName(app.getName(), instanceLabel));
setWindowInstanceLabel(instanceLabel);
}

if (isDriverBootSmokeMode(process.env)) {
app.whenReady().then(() => {
runDriverBootSmokeInProduction();
Expand Down
32 changes: 32 additions & 0 deletions packages/desktop/src/main/instance-identity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, test } from 'bun:test';
import { formatInstanceAppName, resolveInstanceLabel } from './instance-identity.ts';

describe('resolveInstanceLabel', () => {
test('returns null for the default install userData names', () => {
expect(resolveInstanceLabel('/Users/me/Library/Application Support/OpenKnowledge')).toBeNull();
expect(resolveInstanceLabel('/Users/me/Library/Application Support/Open Knowledge')).toBeNull();
expect(resolveInstanceLabel('/tmp/Electron')).toBeNull();
});

test('uses the userData basename for launcher-style instance dirs', () => {
expect(resolveInstanceLabel('/Users/me/.ok/instances/work')).toBe('work');
expect(resolveInstanceLabel('/Users/me/.ok/instances/review-2')).toBe('review-2');
});

test('unwraps the dev OK_INSTANCE sibling-dir form to the bare name', () => {
expect(resolveInstanceLabel('/Users/me/Library/Application Support/OpenKnowledge (a)')).toBe(
'a',
);
expect(resolveInstanceLabel('/data/Open Knowledge (feature-x)')).toBe('feature-x');
});

test('returns null when the basename trims to empty', () => {
expect(resolveInstanceLabel('/Users/me/.ok/instances/ ')).toBeNull();
});
});

describe('formatInstanceAppName', () => {
test('suffixes the app name with the label', () => {
expect(formatInstanceAppName('OpenKnowledge', 'work')).toBe('OpenKnowledge (work)');
});
});
17 changes: 17 additions & 0 deletions packages/desktop/src/main/instance-identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { basename } from 'node:path';

const DEFAULT_USERDATA_NAMES = new Set(['OpenKnowledge', 'Open Knowledge', 'Electron']);

const DEV_WRAPPER = /^(?:OpenKnowledge|Open Knowledge) \((.+)\)$/;

export function resolveInstanceLabel(userDataDir: string): string | null {
const base = basename(userDataDir);
if (DEFAULT_USERDATA_NAMES.has(base)) return null;
const wrapped = DEV_WRAPPER.exec(base);
const label = (wrapped ? wrapped[1] : base).trim();
return label.length > 0 ? label : null;
}

export function formatInstanceAppName(appName: string, label: string): string {
return `${appName} (${label})`;
}
9 changes: 8 additions & 1 deletion packages/desktop/src/main/window-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ function isValidLockPidLocal(value: unknown): value is number {
return true;
}

let windowInstanceLabel: string | null = null;

export function setWindowInstanceLabel(label: string | null): void {
windowInstanceLabel = label;
}

function formatEditorTitle(projectName: string): string {
return `${projectName} — OpenKnowledge`;
const suffix = windowInstanceLabel ? ` (${windowInstanceLabel})` : '';
return `${projectName} — OpenKnowledge${suffix}`;
}

export interface BrowserWindowLike {
Expand Down