Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:

jobs:
claude-review:
if: github.event.pull_request.head.repo.full_name == github.repository
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
Expand Down Expand Up @@ -41,4 +42,3 @@ jobs:
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
cache: npm

- name: Install dependencies
run: npm ci
run: npm ci --force

- name: Run node test suite
run: npm test
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"build": "npm run build:main && npm run build:renderer && npm run build:native",
"build:main": "tsc -p tsconfig.main.json && cp src/main/emoji-data.json dist/main/emoji-data.json",
"build:renderer": "vite build",
"measure:launcher-command-list": "node scripts/measure-launcher-command-list-render.mjs",
"check:i18n": "node scripts/check-i18n.mjs",
"test": "node --test 'scripts/test-*.mjs'",
"build:native": "node scripts/build-native.mjs",
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-native.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const swift = [
['dist/native/menu-item-search', 'src/native/menu-item-search.swift',
'-framework AppKit -framework ApplicationServices'],
['dist/native/emoji-trigger-monitor',
'src/native/emoji-trigger-monitor.swift src/native/ax-caret-query.swift',
'src/native/emoji-trigger-monitor.swift src/native/ax-caret-query.swift src/native/emoji-caret-session-cache.swift',
'-framework AppKit -framework ApplicationServices'],
['dist/native/hotkey-hold-monitor', 'src/native/hotkey-hold-monitor.swift',
'-framework CoreGraphics -framework AppKit -framework Carbon'],
Expand Down
135 changes: 135 additions & 0 deletions scripts/measure-icon-resolution.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env node

import { build } from 'esbuild';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { performance } from 'node:perf_hooks';
import { fileURLToPath, pathToFileURL } from 'node:url';

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const targetFile = path.join(root, 'src/renderer/src/raycast-api/icon-runtime-phosphor.tsx');
const outputFile = path.join(os.tmpdir(), `supercmd-icon-resolution-${process.pid}-${Date.now()}.mjs`);

const exactInputs = [
'AddPerson',
'Icon.ArrowLeftCircleFilled',
'MagnifyingGlass',
'Stopwatch',
'Temperature',
'Dot',
'XMarkCircleFilled',
'Folder',
];

const unknownAndFuzzyInputs = [
'TotallyMissingIconName',
'HyperSpecificSparkleClockWidget',
'SuperCmdTimerStopwatchShape',
'UnmappedTerminalCommandLineIcon',
'MysteryNetworkCloudBolt',
'Noise___No_Such_Icon_999',
];

function exposeResolverPlugin() {
const normalizedTarget = path.normalize(targetFile);
return {
name: 'expose-icon-resolution-for-measurement',
setup(buildApi) {
buildApi.onLoad({ filter: /icon-runtime-phosphor\.tsx$/ }, async (args) => {
if (path.normalize(args.path) !== normalizedTarget) return undefined;
const source = await fs.readFile(args.path, 'utf8');
return {
contents: `${source}\nexport const __measureResolvePhosphorIconFromRaycast = resolvePhosphorIconFromRaycast;\n`,
loader: 'tsx',
resolveDir: path.dirname(args.path),
};
});
},
};
}

function stubServerRenderingPlugin() {
return {
name: 'stub-server-rendering-for-measurement',
setup(buildApi) {
buildApi.onResolve({ filter: /^react-dom\/server$/ }, () => ({
path: 'react-dom-server-measurement-stub',
namespace: 'measurement-stub',
}));
buildApi.onLoad({ filter: /.*/, namespace: 'measurement-stub' }, () => ({
contents: 'export function renderToStaticMarkup() { return ""; }',
loader: 'js',
}));
},
};
}

function measureCase(resolveIcon, { name, inputs, iterations }) {
const calls = inputs.length * iterations;
const start = performance.now();
let resolved = 0;

for (let iteration = 0; iteration < iterations; iteration += 1) {
for (const input of inputs) {
if (resolveIcon(input)?.icon) resolved += 1;
}
}

const durationMs = performance.now() - start;
return {
name,
calls,
resolved,
durationMs: Number(durationMs.toFixed(3)),
callsPerMs: Number((calls / Math.max(durationMs, 0.001)).toFixed(3)),
};
}

async function main() {
await build({
entryPoints: [targetFile],
outfile: outputFile,
bundle: true,
format: 'esm',
platform: 'node',
target: 'es2022',
jsx: 'automatic',
logLevel: 'silent',
plugins: [exposeResolverPlugin(), stubServerRenderingPlugin()],
});

const moduleUrl = `${pathToFileURL(outputFile).href}?t=${Date.now()}`;
const runtime = await import(moduleUrl);
const resolveIcon = runtime.__measureResolvePhosphorIconFromRaycast;
if (typeof resolveIcon !== 'function') {
throw new Error('Failed to expose resolvePhosphorIconFromRaycast for measurement.');
}

const results = [
measureCase(resolveIcon, {
name: 'exact/repeated',
inputs: exactInputs,
iterations: 2500,
}),
measureCase(resolveIcon, {
name: 'unknown-fuzzy/repeated',
inputs: unknownAndFuzzyInputs,
iterations: 250,
}),
];

console.log('Icon resolution measurement');
for (const result of results) {
console.log(
`${result.name}: ${result.calls} calls, ${result.durationMs} ms, ${result.callsPerMs} calls/ms, resolved ${result.resolved}`
);
}
console.log(JSON.stringify({ results }, null, 2));
}

try {
await main();
} finally {
await fs.unlink(outputFile).catch(() => {});
}
Loading
Loading