From a1d8c7bb3072c3fb42aa8cd8921c6f47585abf50 Mon Sep 17 00:00:00 2001 From: Yannic Charlon <52761674+JustYannicc@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:17:20 +0200 Subject: [PATCH 1/8] ci: add project regression checks --- .github/workflows/project-checks.yml | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/project-checks.yml diff --git a/.github/workflows/project-checks.yml b/.github/workflows/project-checks.yml new file mode 100644 index 00000000..28304e1d --- /dev/null +++ b/.github/workflows/project-checks.yml @@ -0,0 +1,56 @@ +name: Project Checks + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: project-checks-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-i18n-build: + name: Tests, i18n, and build + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + CI: true + ELECTRON_SKIP_BINARY_DOWNLOAD: '1' + SUPERCMD_SKIP_ELECTRON_TESTS: '1' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: package-lock.json + + - name: Install dependencies without native postinstall scripts + # package.json pins darwin esbuild packages for release packaging; --force lets Ubuntu install the lockfile. + run: npm ci --ignore-scripts --force + + - name: Run tests + run: npm test + + - name: Check i18n + run: npm run check:i18n + + - name: Build main process + run: npm run build:main + + - name: Build renderer + run: npm run build:renderer From a1225c679dadad5af9066cd4aca616105be14d42 Mon Sep 17 00:00:00 2001 From: Yannic Charlon <52761674+JustYannicc@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:18:05 +0200 Subject: [PATCH 2/8] Add bundled TS import helper for tests --- scripts/lib/ts-import.mjs | 305 ++++++++++++++++++++++-- scripts/test-browser-search-lag-fix.mjs | 28 ++- 2 files changed, 316 insertions(+), 17 deletions(-) diff --git a/scripts/lib/ts-import.mjs b/scripts/lib/ts-import.mjs index 49df6079..89523936 100644 --- a/scripts/lib/ts-import.mjs +++ b/scripts/lib/ts-import.mjs @@ -1,16 +1,293 @@ -// Import a self-contained TypeScript module from a test by transpiling it with -// esbuild on the fly. This lets the recovery tests run the *real* production -// source (renderer-recovery.ts, reload-budget.ts) instead of grepping it. -// -// Only works for modules with no relative imports of their own — the recovery -// helpers are deliberately dependency-free for exactly this reason. - -import { transform } from 'esbuild'; -import fs from 'node:fs'; - -export async function importTs(absPath) { - const src = fs.readFileSync(absPath, 'utf8'); - const { code } = await transform(src, { loader: 'ts', format: 'esm' }); - const dataUrl = 'data:text/javascript;base64,' + Buffer.from(code).toString('base64'); +// Import a TypeScript/TSX module from a test by bundling it with esbuild on the +// fly. Bundling lets tests execute selected production modules that have local +// imports instead of falling back to grep-only assertions. + +import { build } from 'esbuild'; +import path from 'node:path'; + +let importNonce = 0; + +const DEFAULT_STUB_MODULES = new Set([ + '@phosphor-icons/react', + '@raycast/api', + 'electron', + 'electron-liquid-glass', + 'lucide-react', + 'react', + 'react-dom', + 'react-dom/client', + 'react/jsx-dev-runtime', + 'react/jsx-runtime', +]); + +const ASSET_RE = /\.(?:css|less|sass|scss|svg|png|jpe?g|gif|webp|ico|icns|woff2?|ttf|otf|eot)$/i; + +export async function importTs(absPath, options = {}) { + const resolvedPath = path.resolve(absPath); + const { code } = await bundleTs(resolvedPath, options); + const dataUrl = [ + 'data:text/javascript;base64,', + Buffer.from(code).toString('base64'), + `#${encodeURIComponent(resolvedPath)}-${importNonce++}`, + ].join(''); return import(dataUrl); } + +export async function bundleTs(absPath, options = {}) { + const result = await build({ + absWorkingDir: options.root || process.cwd(), + entryPoints: [path.resolve(absPath)], + bundle: true, + write: false, + format: 'esm', + platform: 'node', + target: options.target || 'node20', + sourcemap: options.sourcemap || false, + logLevel: 'silent', + banner: options.browserGlobals === false ? undefined : { js: BROWSER_GLOBALS_BANNER }, + define: { + 'process.env.NODE_ENV': '"test"', + ...(options.define || {}), + }, + loader: { + '.js': 'js', + '.jsx': 'jsx', + '.ts': 'ts', + '.tsx': 'tsx', + '.json': 'json', + ...(options.loader || {}), + }, + external: options.external || [], + plugins: [ + tsImportStubPlugin(options), + ...(options.plugins || []), + ], + }); + + return { code: result.outputFiles[0].text }; +} + +function tsImportStubPlugin(options) { + const stubModules = new Set([...DEFAULT_STUB_MODULES, ...Object.keys(options.stubs || {})]); + for (const specifier of options.stubModules || []) stubModules.add(specifier); + + return { + name: 'ts-import-test-stubs', + setup(esbuild) { + esbuild.onResolve({ filter: /.*/ }, (args) => { + if (stubModules.has(args.path) || ASSET_RE.test(args.path)) { + return { path: args.path, namespace: 'ts-import-stub' }; + } + return null; + }); + + esbuild.onLoad({ filter: /.*/, namespace: 'ts-import-stub' }, (args) => ({ + contents: options.stubs?.[args.path] || defaultStubFor(args.path), + loader: 'js', + })); + }, + }; +} + +function defaultStubFor(specifier) { + if (specifier === 'electron') return ELECTRON_STUB; + if (specifier === 'react') return REACT_STUB; + if (specifier === 'react/jsx-runtime' || specifier === 'react/jsx-dev-runtime') return JSX_RUNTIME_STUB; + if (specifier === 'react-dom' || specifier === 'react-dom/client') return REACT_DOM_STUB; + if (ASSET_RE.test(specifier)) return ASSET_STUB; + return GENERIC_PROXY_STUB; +} + +const BROWSER_GLOBALS_BANNER = ` +const __scNoop = () => {}; +const __scMakeStorage = () => { + const map = new Map(); + return { + getItem: (key) => map.has(String(key)) ? map.get(String(key)) : null, + setItem: (key, value) => { map.set(String(key), String(value)); }, + removeItem: (key) => { map.delete(String(key)); }, + clear: () => { map.clear(); }, + }; +}; +var document = globalThis.document || { + addEventListener: __scNoop, + removeEventListener: __scNoop, + createElement: () => ({ style: {}, setAttribute: __scNoop, appendChild: __scNoop, remove: __scNoop }), + body: { appendChild: __scNoop, removeChild: __scNoop }, +}; +var navigator = globalThis.navigator || { platform: 'test', userAgent: 'node' }; +var localStorage = globalThis.localStorage || __scMakeStorage(); +var sessionStorage = globalThis.sessionStorage || __scMakeStorage(); +var window = globalThis.window || { + document, + navigator, + localStorage, + sessionStorage, + addEventListener: __scNoop, + removeEventListener: __scNoop, + dispatchEvent: () => true, + electron: {}, + location: { href: 'about:blank', reload: __scNoop }, +}; +var requestAnimationFrame = globalThis.requestAnimationFrame || ((callback) => setTimeout(() => callback(Date.now()), 0)); +var cancelAnimationFrame = globalThis.cancelAnimationFrame || ((id) => clearTimeout(id)); +`; + +const ELECTRON_STUB = ` +const noop = () => {}; +const asyncNoop = async () => undefined; +const home = process.env.HOME || process.cwd(); +const paths = { + appData: process.cwd(), + desktop: home + '/Desktop', + documents: home + '/Documents', + downloads: home + '/Downloads', + home, + temp: process.env.TMPDIR || process.cwd(), + userData: process.env.SUPERCMD_TEST_USER_DATA || process.cwd(), +}; +const app = { + isPackaged: false, + getAppPath: () => process.cwd(), + getName: () => 'SuperCmd Test', + getPath: (name) => paths[name] || process.cwd(), + getVersion: () => '0.0.0-test', + isReady: () => true, + whenReady: asyncNoop, + on: noop, + once: noop, + quit: noop, + relaunch: noop, + requestSingleInstanceLock: () => true, + setAsDefaultProtocolClient: () => true, +}; +class BrowserWindow { + constructor() { + this.webContents = { + executeJavaScript: asyncNoop, + on: noop, + once: noop, + send: noop, + setWindowOpenHandler: noop, + }; + } + loadFile() { return Promise.resolve(); } + loadURL() { return Promise.resolve(); } + on() {} + once() {} + show() {} + hide() {} + close() {} + destroy() {} + isDestroyed() { return false; } +} +const ipcMain = { handle: noop, on: noop, once: noop, removeAllListeners: noop, removeHandler: noop }; +const ipcRenderer = { invoke: asyncNoop, send: noop, on: noop, once: noop, removeAllListeners: noop, removeListener: noop }; +const shell = { openExternal: asyncNoop, openPath: async () => '', showItemInFolder: noop }; +const dialog = { + showErrorBox: noop, + showMessageBox: async () => ({ response: 0 }), + showOpenDialog: async () => ({ canceled: true, filePaths: [] }), + showSaveDialog: async () => ({ canceled: true, filePath: '' }), +}; +const clipboard = { + readText: () => '', + writeText: noop, + readImage: () => ({ isEmpty: () => true, toDataURL: () => '' }), + writeImage: noop, +}; +const nativeTheme = { shouldUseDarkColors: false, on: noop, removeListener: noop }; +const screen = { + getAllDisplays: () => [], + getPrimaryDisplay: () => ({ bounds: { x: 0, y: 0, width: 1024, height: 768 }, workArea: { x: 0, y: 0, width: 1024, height: 768 }, scaleFactor: 1 }), +}; +const safeStorage = { + decryptString: (value) => Buffer.from(value).toString('utf8'), + encryptString: (value) => Buffer.from(String(value)), + isEncryptionAvailable: () => false, +}; +module.exports = { app, BrowserWindow, clipboard, dialog, ipcMain, ipcRenderer, nativeTheme, safeStorage, screen, shell }; +`; + +const REACT_STUB = ` +const noop = () => {}; +const createElement = (type, props, ...children) => ({ type, props: { ...(props || {}), children } }); +class Component { + constructor(props) { + this.props = props || {}; + this.state = {}; + } + setState(update) { + this.state = { ...this.state, ...(typeof update === 'function' ? update(this.state, this.props) : update) }; + } +} +const createContext = (value) => ({ + Consumer: ({ children }) => typeof children === 'function' ? children(value) : children, + Provider: ({ children }) => children, + _currentValue: value, +}); +const React = { + Component, + PureComponent: Component, + Fragment: Symbol.for('react.fragment'), + createContext, + createElement, + createRef: () => ({ current: null }), + forwardRef: (render) => render, + lazy: (loader) => loader, + memo: (component) => component, + startTransition: (callback) => callback(), + useCallback: (callback) => callback, + useContext: (context) => context?._currentValue, + useEffect: noop, + useId: () => 'test-id', + useLayoutEffect: noop, + useMemo: (factory) => factory(), + useReducer: (reducer, initialArg, init) => [init ? init(initialArg) : initialArg, noop], + useRef: (value) => ({ current: value }), + useState: (initial) => [typeof initial === 'function' ? initial() : initial, noop], +}; +module.exports = React; +module.exports.default = React; +module.exports.__esModule = true; +`; + +const JSX_RUNTIME_STUB = ` +const Fragment = Symbol.for('react.fragment'); +const jsx = (type, props, key) => ({ type, key, props: props || {} }); +module.exports = { Fragment, jsx, jsxs: jsx }; +module.exports.default = module.exports; +module.exports.__esModule = true; +`; + +const REACT_DOM_STUB = ` +const root = { render() {}, unmount() {} }; +module.exports = { + createPortal: (children) => children, + createRoot: () => root, + hydrateRoot: () => root, + render() {}, + unmountComponentAtNode() {}, +}; +module.exports.default = module.exports; +module.exports.__esModule = true; +`; + +const ASSET_STUB = ` +module.exports = ''; +module.exports.default = ''; +`; + +const GENERIC_PROXY_STUB = ` +const makeStub = (name = 'stub') => new Proxy(function stub() {}, { + apply: () => undefined, + construct: () => ({}), + get: (_target, prop) => { + if (prop === '__esModule') return true; + if (prop === Symbol.toStringTag) return 'Module'; + return makeStub(String(prop)); + }, +}); +module.exports = makeStub(); +module.exports.default = module.exports; +`; diff --git a/scripts/test-browser-search-lag-fix.mjs b/scripts/test-browser-search-lag-fix.mjs index bff0d368..1f8eea4d 100644 --- a/scripts/test-browser-search-lag-fix.mjs +++ b/scripts/test-browser-search-lag-fix.mjs @@ -3,6 +3,11 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { importTs } from './lib/ts-import.mjs'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const files = { history: fs.readFileSync('src/main/browser-search-history.ts', 'utf8'), @@ -24,9 +29,26 @@ function assertNotIncludes(source, needle) { } test('Browser search lag fix', async (t) => { - await t.test('main history module exports required functions', () => { - assertIncludes(files.history, 'getBrowserSearchRevision'); - assertIncludes(files.history, 'getBrowserSearchStats'); + // Baseline: this used to grep browser-search-history.ts for export names, + // which could not prove the module or its relative imports actually execute. + await t.test('main history module imports exported browser-search API', async () => { + const history = await importTs(path.join(root, 'src/main/browser-search-history.ts')); + + assert.equal(typeof history.getBrowserSearchRevision, 'function'); + assert.equal(typeof history.getBrowserSearchStats, 'function'); + assert.equal(typeof history.resolveInput, 'function'); + + const url = history.resolveInput('example.com'); + assert.equal(url?.type, 'url'); + assert.equal(url?.url, 'https://example.com/'); + assert.equal(url?.host, 'example.com'); + + const search = history.resolveInput('browser search lag'); + assert.equal(search?.type, 'search'); + assert.equal(search?.url, 'https://www.google.com/search?q=browser%20search%20lag'); + }); + + await t.test('profile bookmark import keeps dedupe state', () => { assertIncludes(files.history, 'seenBookmarkKeys'); assertIncludes(files.history, 'existingBookmarkByKey'); }); From 0799e71b339617bac64aa40b15be49d2fa9a967d Mon Sep 17 00:00:00 2001 From: Yannic Charlon <52761674+JustYannicc@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:30:08 +0200 Subject: [PATCH 3/8] fix(types): make renderer typecheck clean --- src/renderer/src/App.tsx | 2 +- src/renderer/src/CameraExtension.tsx | 2 +- src/renderer/src/QuickLinkManager.tsx | 2 +- src/renderer/src/SnippetManager.tsx | 2 +- src/renderer/src/hooks/useAiChat.ts | 2 +- .../src/hooks/useLauncherCommandModel.ts | 2 +- .../src/hooks/useLauncherKeyboardControls.ts | 1 + src/renderer/src/i18n/runtime.ts | 4 +++- .../raycast-api/action-runtime-overlay.tsx | 3 ++- .../src/raycast-api/context-scope-runtime.ts | 2 +- .../src/raycast-api/detail-runtime.tsx | 4 ++-- .../raycast-api/hooks/use-cached-promise.ts | 4 ++-- .../src/raycast-api/icon-runtime-phosphor.tsx | 2 +- src/renderer/src/raycast-api/index.tsx | 20 ++++++++-------- .../raycast-api/list-runtime-renderers.tsx | 4 ++-- src/renderer/src/raycast-api/list-runtime.tsx | 4 ++-- .../raycast-api/oauth/oauth-service-core.ts | 4 ++++ .../raycast-api/oauth/with-access-token.tsx | 7 +++--- src/renderer/src/settings/AITab.tsx | 7 ++++-- src/renderer/src/settings/StoreTab.tsx | 1 + .../src/types/liquid-glass-react.d.ts | 23 +++++++++++++++++++ src/renderer/src/utils/quicklink-icons.tsx | 11 ++++----- 22 files changed, 73 insertions(+), 40 deletions(-) create mode 100644 src/renderer/src/types/liquid-glass-react.d.ts diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index a49482ba..aeb00cd4 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1076,7 +1076,7 @@ const App: React.FC = () => { const pinToggleForCommand = useCallback( async (command: CommandInfo) => { - console.log('[PIN-TOGGLE] called for command:', command?.id, command?.name); + console.log('[PIN-TOGGLE] called for command:', command?.id, command?.title); const currentPinned = pinnedCommandsRef.current; const exists = currentPinned.includes(command.id); console.log('[PIN-TOGGLE] currentPinned:', currentPinned, 'exists:', exists); diff --git a/src/renderer/src/CameraExtension.tsx b/src/renderer/src/CameraExtension.tsx index 7de6a3f8..02d43a3b 100644 --- a/src/renderer/src/CameraExtension.tsx +++ b/src/renderer/src/CameraExtension.tsx @@ -298,7 +298,7 @@ const CameraExtension: React.FC = ({ onClose }) => { }, 5000); capturePreviewClearTimerRef.current = window.setTimeout(() => { setCapturePreviewDataUrl(null); - setCapturePreviewClearTimerRef.current = null; + capturePreviewClearTimerRef.current = null; }, 5300); setFlashVisible(true); if (flashTimerRef.current != null) { diff --git a/src/renderer/src/QuickLinkManager.tsx b/src/renderer/src/QuickLinkManager.tsx index 2f749757..deaea4c2 100644 --- a/src/renderer/src/QuickLinkManager.tsx +++ b/src/renderer/src/QuickLinkManager.tsx @@ -1286,7 +1286,7 @@ const QuickLinkManager: React.FC = ({ onClose, initialVie const inputRef = useRef(null); const inlineArgumentLaneRef = useRef(null); const inlineArgumentClusterRef = useRef(null); - const inlineDynamicInputRefs = useRef>([]); + const inlineDynamicInputRefs = useRef>([]); const firstDynamicInputRef = useRef(null); const listRef = useRef(null); const itemRefs = useRef<(HTMLDivElement | null)[]>([]); diff --git a/src/renderer/src/SnippetManager.tsx b/src/renderer/src/SnippetManager.tsx index 3e2e8c65..e5ef51dd 100644 --- a/src/renderer/src/SnippetManager.tsx +++ b/src/renderer/src/SnippetManager.tsx @@ -432,7 +432,7 @@ const SnippetManager: React.FC = ({ onClose, initialView }) const inputRef = useRef(null); const inlineArgumentLaneRef = useRef(null); const inlineArgumentClusterRef = useRef(null); - const inlineArgumentInputRefs = useRef>([]); + const inlineArgumentInputRefs = useRef>([]); const firstDynamicInputRef = useRef(null); const listRef = useRef(null); const itemRefs = useRef<(HTMLDivElement | null)[]>([]); diff --git a/src/renderer/src/hooks/useAiChat.ts b/src/renderer/src/hooks/useAiChat.ts index fea32c94..bce0b4a5 100644 --- a/src/renderer/src/hooks/useAiChat.ts +++ b/src/renderer/src/hooks/useAiChat.ts @@ -36,7 +36,7 @@ export interface UseAiChatReturn { setAiQuery: (value: string) => void; aiInputRef: React.RefObject; aiResponseRef: React.RefObject; - setAiAvailable: (value: boolean) => void; + setAiAvailable: React.Dispatch>; conversations: AiConversation[]; activeConversationId: string | null; startAiChat: (searchQuery: string) => void; diff --git a/src/renderer/src/hooks/useLauncherCommandModel.ts b/src/renderer/src/hooks/useLauncherCommandModel.ts index 44ca0fbc..ff6174e3 100644 --- a/src/renderer/src/hooks/useLauncherCommandModel.ts +++ b/src/renderer/src/hooks/useLauncherCommandModel.ts @@ -187,7 +187,7 @@ function getFileDepthPenalty(result: IndexedFileSearchResult): number { type BrowserLauncherProfile = { id?: string; - browserId?: BrowserSearchSource | string; + browserId?: BrowserSearchSource; displayName: string; detectedName?: string; profileId: string; diff --git a/src/renderer/src/hooks/useLauncherKeyboardControls.ts b/src/renderer/src/hooks/useLauncherKeyboardControls.ts index a385562f..e965d9f1 100644 --- a/src/renderer/src/hooks/useLauncherKeyboardControls.ts +++ b/src/renderer/src/hooks/useLauncherKeyboardControls.ts @@ -91,6 +91,7 @@ export type UseLauncherKeyboardControlsOptions = { kind?: CommandInfo['browserResultKind']; url?: string; sourceProfileId?: string; + openInSourceProfile?: boolean; windowId?: string | number; tabId?: string | number; } diff --git a/src/renderer/src/i18n/runtime.ts b/src/renderer/src/i18n/runtime.ts index edf181c9..e64e92dd 100644 --- a/src/renderer/src/i18n/runtime.ts +++ b/src/renderer/src/i18n/runtime.ts @@ -12,7 +12,9 @@ import itMessages from './locales/it.json'; export type SupportedAppLocale = 'en' | 'zh-Hans' | 'zh-Hant' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'ru' | 'it'; export type AppLanguageSetting = 'system' | SupportedAppLocale; export type TranslationValues = Record; -type MessageTree = Record; +interface MessageTree { + [key: string]: string | MessageTree; +} export const DEFAULT_APP_LANGUAGE: AppLanguageSetting = 'system'; export const FALLBACK_APP_LOCALE: SupportedAppLocale = 'en'; diff --git a/src/renderer/src/raycast-api/action-runtime-overlay.tsx b/src/renderer/src/raycast-api/action-runtime-overlay.tsx index 02896d10..e623136c 100644 --- a/src/renderer/src/raycast-api/action-runtime-overlay.tsx +++ b/src/renderer/src/raycast-api/action-runtime-overlay.tsx @@ -112,7 +112,8 @@ export function createActionOverlayRuntime(deps: OverlayDeps) { } if (source && typeof source === 'object') { - const variants = [source.light, source.dark].filter((value): value is string => typeof value === 'string' && value.trim().length > 0); + const sourceVariants = source as Record; + const variants = [sourceVariants.light, sourceVariants.dark].filter((value): value is string => typeof value === 'string' && value.trim().length > 0); if (variants.length > 0) { const assetLikeVariants = variants.filter((value) => hasImageExtension(value)); if (assetLikeVariants.length === 0) return true; diff --git a/src/renderer/src/raycast-api/context-scope-runtime.ts b/src/renderer/src/raycast-api/context-scope-runtime.ts index 440b0bc1..410737cc 100644 --- a/src/renderer/src/raycast-api/context-scope-runtime.ts +++ b/src/renderer/src/raycast-api/context-scope-runtime.ts @@ -89,7 +89,7 @@ export function withExtensionContext(ctx: ExtensionContextSnapshot | undefine try { const value = fn(); if (value && typeof (value as any).then === 'function') { - return (value as Promise).finally(restore) as T; + return (value as unknown as Promise).finally(restore) as T; } restore(); return value; diff --git a/src/renderer/src/raycast-api/detail-runtime.tsx b/src/renderer/src/raycast-api/detail-runtime.tsx index 88d513ff..0566bb26 100644 --- a/src/renderer/src/raycast-api/detail-runtime.tsx +++ b/src/renderer/src/raycast-api/detail-runtime.tsx @@ -90,7 +90,7 @@ export function createDetailRuntime(deps: CreateDetailRuntimeDeps) { for (const node of allChildren) { if (React.isValidElement(node)) { - const typeRecord = node.type as Record | null; + const typeRecord = node.type as unknown as Record | null; if (typeRecord?.[DETAIL_METADATA_RUNTIME_MARKER] === true) { metadataNodes.push(node); continue; @@ -290,7 +290,7 @@ export function createDetailRuntime(deps: CreateDetailRuntimeDeps) { ); const MetadataComponent = ({ children }: { children?: React.ReactNode }) =>
{children}
; - (MetadataComponent as Record)[DETAIL_METADATA_RUNTIME_MARKER] = true; + (MetadataComponent as unknown as Record)[DETAIL_METADATA_RUNTIME_MARKER] = true; MetadataComponent.displayName = 'Detail.Metadata'; const Metadata = Object.assign( diff --git a/src/renderer/src/raycast-api/hooks/use-cached-promise.ts b/src/renderer/src/raycast-api/hooks/use-cached-promise.ts index 7d1f4c4e..0f125309 100644 --- a/src/renderer/src/raycast-api/hooks/use-cached-promise.ts +++ b/src/renderer/src/raycast-api/hooks/use-cached-promise.ts @@ -96,7 +96,7 @@ export function useCachedPromise( if (pageNum === 0) { setAccumulatedData(Array.isArray(pageData) ? pageData : []); } else { - setAccumulatedData((prev) => { + setAccumulatedData((prev: unknown) => { const prevArr = Array.isArray(prev) ? prev : []; const newArr = Array.isArray(pageData) ? pageData : []; return [...prevArr, ...newArr]; @@ -123,7 +123,7 @@ export function useCachedPromise( if (pageNum === 0) { setAccumulatedData(Array.isArray(pageData) ? pageData : []); } else { - setAccumulatedData((prev) => { + setAccumulatedData((prev: unknown) => { const prevArr = Array.isArray(prev) ? prev : []; const newArr = Array.isArray(pageData) ? pageData : []; return [...prevArr, ...newArr]; diff --git a/src/renderer/src/raycast-api/icon-runtime-phosphor.tsx b/src/renderer/src/raycast-api/icon-runtime-phosphor.tsx index 2716a240..c7dcf1ab 100644 --- a/src/renderer/src/raycast-api/icon-runtime-phosphor.tsx +++ b/src/renderer/src/raycast-api/icon-runtime-phosphor.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; -import * as Phosphor from '../../../../node_modules/@phosphor-icons/react/dist/index.es.js'; +import * as Phosphor from '@phosphor-icons/react'; import { RAYCAST_ICON_NAMES, RAYCAST_ICON_VALUE_TO_NAME, type RaycastIconName } from './raycast-icon-enum'; type PhosphorIconWeight = 'thin' | 'light' | 'regular' | 'bold' | 'fill' | 'duotone'; diff --git a/src/renderer/src/raycast-api/index.tsx b/src/renderer/src/raycast-api/index.tsx index 95940389..f3e2ddf6 100644 --- a/src/renderer/src/raycast-api/index.tsx +++ b/src/renderer/src/raycast-api/index.tsx @@ -434,8 +434,10 @@ export enum ToastStyle { Failure = 'failure', } +type ToastStyleInput = ToastStyle | 'animated' | 'success' | 'failure'; + export class Toast { - static Style = ToastStyle; + static Style: typeof ToastStyle = ToastStyle; private static _activeToast: Toast | null = null; private _title = ''; @@ -483,7 +485,7 @@ export class Toast { return this._style; } - public set style(value: ToastStyle | Toast.Style | string) { + public set style(value: ToastStyleInput | Toast.Style | string) { this._style = this.normalizeStyle(value); this.refresh(); } @@ -506,7 +508,7 @@ export class Toast { this.refresh(); } - private normalizeStyle(value: ToastStyle | Toast.Style | string | undefined): ToastStyle { + private normalizeStyle(value: ToastStyleInput | Toast.Style | string | undefined): ToastStyle { if (value === ToastStyle.Animated || value === Toast.Style.Animated || value === 'animated') { return ToastStyle.Animated; } @@ -894,16 +896,12 @@ export class Toast { // Toast namespace for types (merged with class) export namespace Toast { - export enum Style { - Animated = 'animated', - Success = 'success', - Failure = 'failure', - } + export type Style = ToastStyle; export interface Options { title: string; message?: string; - style?: ToastStyle | Toast.Style; + style?: ToastStyleInput | Toast.Style; primaryAction?: Alert.ActionOptions; secondaryAction?: Alert.ActionOptions; } @@ -924,9 +922,9 @@ function shouldSuppressBenignGitMissingPathToast(options: Toast.Options): boolea } export async function showToast(options: Toast.Options): Promise; -export async function showToast(style: ToastStyle | Toast.Style, title: string, message?: string): Promise; +export async function showToast(style: ToastStyleInput | Toast.Style, title: string, message?: string): Promise; export async function showToast( - optionsOrStyle: Toast.Options | ToastStyle | Toast.Style, + optionsOrStyle: Toast.Options | ToastStyleInput | Toast.Style, title?: string, message?: string ): Promise { diff --git a/src/renderer/src/raycast-api/list-runtime-renderers.tsx b/src/renderer/src/raycast-api/list-runtime-renderers.tsx index 44d20a55..cd14b3b3 100644 --- a/src/renderer/src/raycast-api/list-runtime-renderers.tsx +++ b/src/renderer/src/raycast-api/list-runtime-renderers.tsx @@ -19,7 +19,7 @@ interface ListRendererDeps { renderIcon: (icon: any, className?: string, assetsPath?: string) => React.ReactNode; resolveTintColor: (tintColor?: string) => string | undefined; resolveReadableTintColor: (tintColor?: string, options?: { minContrast?: number }) => string | undefined; - addHexAlpha: (hex: string, alphaHex?: string) => string | null; + addHexAlpha: (hex: string, alphaHex: string) => string | undefined; } export function createListRenderers(deps: ListRendererDeps) { @@ -102,7 +102,7 @@ export function createListRenderers(deps: ListRendererDeps) { {icon &&
{renderIcon(icon, iconClassName, assetsPath)}
}
{primaryText}
{secondaryText && {secondaryText}} - {accessories?.map((accessory, index) => { + {accessories?.map((accessory: NonNullable[number], index: number) => { const accessoryText = typeof accessory?.text === 'string' ? accessory.text : typeof accessory?.text === 'object' ? accessory.text?.value || '' : ''; const accessoryTextColorRaw = typeof accessory?.text === 'object' ? accessory.text?.color : undefined; const tagText = typeof accessory?.tag === 'string' ? accessory.tag : typeof accessory?.tag === 'object' ? accessory.tag?.value || '' : ''; diff --git a/src/renderer/src/raycast-api/list-runtime.tsx b/src/renderer/src/raycast-api/list-runtime.tsx index d7b665bd..c951e63c 100644 --- a/src/renderer/src/raycast-api/list-runtime.tsx +++ b/src/renderer/src/raycast-api/list-runtime.tsx @@ -34,7 +34,7 @@ interface ListRuntimeDeps { renderIcon: (icon: any, className?: string, assetsPath?: string) => React.ReactNode; resolveTintColor: (value?: string) => string | undefined; resolveReadableTintColor: (value?: string, options?: { minContrast?: number }) => string | undefined; - addHexAlpha: (hex: string, alphaHex?: string) => string | null; + addHexAlpha: (hex: string, alphaHex: string) => string | undefined; getExtensionContext: () => { assetsPath: string; extensionDisplayName?: string; @@ -390,7 +390,7 @@ export function createListRuntime(deps: ListRuntimeDeps) { const detailElement = useMemo(() => { if (!rawDetail || !React.isValidElement(rawDetail)) return rawDetail; if (rawDetail.type !== React.Fragment) return rawDetail; - const children = React.Children.toArray(rawDetail.props.children); + const children = React.Children.toArray((rawDetail.props as { children?: React.ReactNode }).children); let mergedMarkdown: string | undefined; let mergedMetadata: React.ReactElement | undefined; let mergedIsLoading: boolean | undefined; diff --git a/src/renderer/src/raycast-api/oauth/oauth-service-core.ts b/src/renderer/src/raycast-api/oauth/oauth-service-core.ts index 0a923781..7fb945ae 100644 --- a/src/renderer/src/raycast-api/oauth/oauth-service-core.ts +++ b/src/renderer/src/raycast-api/oauth/oauth-service-core.ts @@ -25,6 +25,10 @@ export class OAuthServiceCore { return (override && override.trim()) || this.options.clientId; } + getPersonalAccessToken(): string | undefined { + return this.options.personalAccessToken; + } + setClientIdOverride(value: string): void { const key = oauthClientIdOverrideKey(this.getProviderKey()); const trimmed = (value || '').trim(); diff --git a/src/renderer/src/raycast-api/oauth/with-access-token.tsx b/src/renderer/src/raycast-api/oauth/with-access-token.tsx index 83b356d8..e2440719 100644 --- a/src/renderer/src/raycast-api/oauth/with-access-token.tsx +++ b/src/renderer/src/raycast-api/oauth/with-access-token.tsx @@ -16,10 +16,11 @@ export function withAccessToken(options: any) { const shouldInvokeOnAuthorize = !(options instanceof OAuthService); const authorizeForNoView = async (): Promise => { if (options instanceof OAuthService) { - if (options?.personalAccessToken) { - accessTokenValue = options.personalAccessToken; + const personalAccessToken = options.getPersonalAccessToken(); + if (personalAccessToken) { + accessTokenValue = personalAccessToken; accessTokenType = 'personal'; - await Promise.resolve(options.onAuthorize?.({ token: accessTokenValue, type: 'personal' })); + await Promise.resolve(options.onAuthorize?.({ token: personalAccessToken, type: 'personal' })); return; } diff --git a/src/renderer/src/settings/AITab.tsx b/src/renderer/src/settings/AITab.tsx index c27befdd..6813afd9 100644 --- a/src/renderer/src/settings/AITab.tsx +++ b/src/renderer/src/settings/AITab.tsx @@ -1224,7 +1224,7 @@ const AITab: React.FC = () => {

{t('settings.ai.llm.ollama.models')}

{ollamaRunning && (