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
10 changes: 5 additions & 5 deletions apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@
"react": 1
},
"importSpecifiers": 9,
"nonTriviaTokens": 2299
"nonTriviaTokens": 2276
},
"src/renderer/app-shell-context-compaction.ts": {
"importDeclarations": 0,
Expand Down Expand Up @@ -419,7 +419,7 @@
"@maka/core/redaction": 1
},
"importSpecifiers": 1,
"nonTriviaTokens": 393
"nonTriviaTokens": 391
},
"src/renderer/app-shell-detail-panel.tsx": {
"importDeclarations": 0,
Expand Down Expand Up @@ -538,7 +538,7 @@
"src/renderer/app-shell-project-actions.ts": {
"importDeclarations": 4,
"bridgePaths": {
"window.maka.app.openPath": 4,
"window.maka.app.openPath": 3,
"window.maka.app.resolveProjectGitInfo": 1,
"window.maka.projects.add": 1,
"window.maka.projects.archive": 1,
Expand All @@ -563,7 +563,7 @@
"./session-workspace-errors": 1
},
"importSpecifiers": 7,
"nonTriviaTokens": 2284
"nonTriviaTokens": 2157
},
"src/renderer/app-shell-revision-actions.ts": {
"importDeclarations": 4,
Expand Down Expand Up @@ -862,7 +862,7 @@
"react": 1
},
"importSpecifiers": 100,
"nonTriviaTokens": 13135
"nonTriviaTokens": 13127
},
"src/renderer/use-app-shell-composer-quotes.ts": {
"importDeclarations": 0,
Expand Down
19 changes: 19 additions & 0 deletions apps/desktop/src/main/__tests__/command-palette-retired.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@ test('a stale in-memory default pointing at a retired connection is not testable
assert.ok(!ids.includes('diag:test-default'));
});

test('the palette opens the Skills surface instead of an ambiguous Skills folder', () => {
const ids = buildCommandList({
locale: 'en',
activeSessionId: undefined,
themePref: 'auto',
connections: [],
defaultSlug: null,
onNewChat: () => {},
onOpenSettings: () => {},
onOpenSettingsSection: () => {},
onOpenShortcuts: () => {},
onSetTheme: () => {},
onSelectModule: () => {},
}).map((command) => command.id);

assert.ok(ids.includes('nav:skills'));
assert.ok(!ids.includes('diag:open-skills'));
});

for (const locale of ['en', 'zh-CN'] as const) {
test(`${locale} static shortcut hints preserve both platform variants`, () => {
const commands = buildCommandList({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ test('every shell error-copy entry classifies, and keeps its contextual fallback
assert.equal(messageRefreshErrorMessage(timeout, 'zh-CN'), '请求超时');
assert.equal(messageRefreshErrorMessage(opaque, 'zh-CN'), copy.errors.messageRefresh);

assert.equal(openPathActionErrorMessage(timeout, 'skills', 'zh-CN'), '请求超时');
assert.equal(openPathActionErrorMessage(timeout, 'workspace', 'zh-CN'), '请求超时');
assert.equal(
openPathActionErrorMessage(opaque, 'skills', 'zh-CN'),
copy.errors.openPath(copy.paths.skills),
openPathActionErrorMessage(opaque, 'workspace', 'zh-CN'),
copy.errors.openPath(copy.paths.workspace),
);

// The connection test derives its own category from the status code, so an
Expand Down
92 changes: 91 additions & 1 deletion apps/desktop/src/main/__tests__/module-hub-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import assert from 'node:assert/strict';
import { afterEach, test } from 'node:test';
import { act, createElement, Fragment } from 'react';
import { act, createElement, Fragment, StrictMode } from 'react';
import type { ScheduledTask } from '@maka/core/scheduled-task';
import { LocaleProvider, ToastProvider } from '@maka/ui';
import {
Expand Down Expand Up @@ -111,6 +111,7 @@ test('controller scoping removes shell-wide work from Module Hub updates', async
const controllerInput = {
selection: { section: 'sessions' } as const,
selectModule: () => undefined,
clientPathsAccessible: false,
useSkillInChat: () => undefined,
openSession: () => undefined,
appendComposerText: () => undefined,
Expand Down Expand Up @@ -295,3 +296,92 @@ test('command port keeps the newest controller through stale cleanup', async ()
await port.copyTodayDailyReview();
assert.deepEqual(calls, ['second:refresh', 'second:create']);
});

for (const capabilityTiming of ['at mount', 'before', 'after'] as const) {
test(`Skills locations load when local path capability arrives ${capabilityTiming} deferred startup`, async () => {
const { root } = installReactRenderer();
const frames = new Map<number, FrameRequestCallback>();
let frameId = 0;
globalThis.requestAnimationFrame = (callback) => {
frames.set(++frameId, callback);
return frameId;
};
globalThis.cancelAnimationFrame = (id) => { frames.delete(id); };
let current: ReturnType<typeof useModuleHubController> | undefined;
let locationReads = 0;
let skillReads = 0;
function skills() {
assert.ok(current);
return current.host.skills;
}
const defaults = createFakeModuleHubServices();
const services = createFakeModuleHubServices({
skills: {
...defaults.skills,
list: async () => { skillReads += 1; return []; },
listLocations: async () => {
locationReads += 1;
return {
contextIds: { workspace: `workspace-${locationReads}` },
locations: [{
ref: 'workspace:legacy', scope: 'workspace', source: 'legacy',
path: `/workspace-${locationReads}/skills`, status: 'available', skillCount: 0,
}],
};
},
},
});
function Probe({ clientPathsAccessible }: { clientPathsAccessible: boolean }) {
current = useModuleHubController({
selection: { section: 'extensions', module: 'skills' },
selectModule: () => undefined,
clientPathsAccessible,
useSkillInChat: () => undefined,
openSession: () => undefined,
appendComposerText: () => undefined,
captureActiveComposerClaim: () => undefined,
});
return null;
}
async function render(clientPathsAccessible: boolean): Promise<void> {
await act(async () => root.render(createElement(StrictMode, null, createElement(LocaleProvider, {
locale: 'en',
children: createElement(ToastProvider, {
children: createElement(ModuleHubServicesProvider, { services },
createElement(Probe, { clientPathsAccessible })),
}),
}))));
}

await render(capabilityTiming === 'at mount');
assert.equal(skillReads, 0);
assert.equal(locationReads, 0);
if (capabilityTiming === 'before') {
await render(true);
assert.equal(skillReads, 0);
assert.equal(locationReads, 0);
}
await act(async () => {
const pending = [...frames.values()];
frames.clear();
for (const frame of pending) frame(0);
});
assert.equal(skillReads, 1);
if (capabilityTiming === 'after') {
assert.equal(locationReads, 0);
assert.deepEqual(skills().skillLocations, []);
await render(true);
}
assert.equal(locationReads, 1);
assert.equal(skills().skillLocations[0]?.path, '/workspace-1/skills');
assert.equal(typeof skills().onOpenSkillLocation, 'function');
await render(false);
assert.equal(locationReads, 1);
assert.deepEqual(skills().skillLocations, []);
assert.equal(skills().onOpenSkillLocation, undefined);
await render(true);
assert.equal(locationReads, 2);
assert.equal(skills().skillLocations[0]?.path, '/workspace-2/skills');
assert.equal(skillReads, 1);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('createDesktopModuleHubServices', () => {
skills: Object.assign(methodRecorder(calls, 'skills'), {
sources: methodRecorder(calls, 'skills.sources'),
catalog: methodRecorder(calls, 'skills.catalog'),
locations: methodRecorder(calls, 'skills.locations'),
}),
scheduledTasks: methodRecorder(calls, 'scheduledTasks'),
dailyReview: methodRecorder(calls, 'dailyReview'),
Expand All @@ -71,6 +72,7 @@ describe('createDesktopModuleHubServices', () => {

assert.deepEqual(await services.runtimeHosts.getDefault(), host);
await services.skills.list(host);
await services.skills.listLocations(host);
await services.skills.listManagedSources(host);
await services.skills.listBundledCatalog(host);
await services.skills.importManagedSource(host);
Expand All @@ -82,6 +84,7 @@ describe('createDesktopModuleHubServices', () => {
await services.skills.setPinned('user:skill', false, host);
await services.skills.delete('user:skill', host);
await services.skills.open('skill', 'directory', host);
await services.skills.openLocation('user:agents', { contextId: 'project-context', createIfMissing: true }, host);

const createInput = { title: 'Task' } as Parameters<
typeof services.scheduledTasks.create
Expand Down Expand Up @@ -110,6 +113,7 @@ describe('createDesktopModuleHubServices', () => {

assert.deepEqual(calls, [
{ name: 'skills.list', args: [host] },
{ name: 'skills.locations.list', args: [host] },
{ name: 'skills.sources.list', args: [host] },
{ name: 'skills.catalog.list', args: [host] },
{ name: 'skills.sources.importLocalFile', args: [host] },
Expand All @@ -121,6 +125,7 @@ describe('createDesktopModuleHubServices', () => {
{ name: 'skills.setPinned', args: ['user:skill', false, host] },
{ name: 'skills.delete', args: ['user:skill', host] },
{ name: 'skills.open', args: ['skill', 'directory', host] },
{ name: 'skills.locations.open', args: ['user:agents', { contextId: 'project-context', createIfMissing: true }, host] },
{ name: 'scheduledTasks.list', args: [host] },
{ name: 'scheduledTasks.create', args: [createInput, host] },
{ name: 'scheduledTasks.update', args: ['task', updateInput, host] },
Expand Down
Loading