From 7670ab007f53b2a6b90dbdb128978917e4a45df7 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Fri, 13 Mar 2026 08:12:21 -0700 Subject: [PATCH] Fix lint errors --- crates-tauri/yaak-license/index.ts | 2 +- crates/yaak-git/index.ts | 12 ++++++------ crates/yaak-sync/index.ts | 4 ++-- packages/plugin-runtime/src/PluginInstance.ts | 7 ++++--- packages/plugin-runtime/src/interceptStdout.ts | 1 + packages/plugin-runtime/tsconfig.json | 6 +----- plugins-external/faker/tests/init.test.ts | 1 + plugins-external/httpsnippet/src/index.ts | 4 ++-- plugins-external/mcp-server/src/index.ts | 2 +- plugins-external/mcp-server/src/server.ts | 4 ++-- plugins/auth-oauth2/src/index.ts | 4 ++-- plugins/filter-jsonpath/src/index.ts | 2 +- plugins/filter-xpath/src/index.ts | 2 +- plugins/importer-insomnia/src/v4.ts | 2 +- plugins/importer-insomnia/src/v5.ts | 4 ++-- .../importer-postman-environment/src/index.ts | 2 +- plugins/importer-postman/src/index.ts | 6 +++--- src-web/commands/openWorkspaceFromSyncDir.tsx | 2 +- src-web/components/DnsOverridesEditor.tsx | 3 ++- src-web/components/EnvironmentEditDialog.tsx | 3 ++- src-web/components/FolderLayout.tsx | 3 ++- src-web/components/ImportCurlButton.tsx | 3 ++- src-web/components/JsonBodyEditor.tsx | 5 +++-- src-web/components/RedirectToLatestWorkspace.tsx | 5 +++-- src-web/components/Sidebar.tsx | 3 ++- .../components/WorkspaceEncryptionSetting.tsx | 2 +- src-web/components/core/Dropdown.tsx | 3 ++- src-web/components/core/Tabs/Tabs.tsx | 3 ++- src-web/components/git/GitDropdown.tsx | 5 +++-- .../components/graphql/GraphQLDocsExplorer.tsx | 10 +++++----- src-web/components/responseViewers/PdfViewer.tsx | 5 +++-- src-web/font-size.ts | 3 ++- src-web/font.ts | 3 ++- src-web/hooks/useActiveRequest.ts | 2 +- src-web/hooks/useCreateDropdownItems.tsx | 4 ++-- src-web/hooks/useFastMutation.ts | 2 +- src-web/hooks/useHttpResponseEvents.ts | 5 +++-- src-web/hooks/usePinnedGrpcConnection.ts | 5 +++-- src-web/hooks/usePinnedWebsocketConnection.ts | 5 +++-- src-web/hooks/useWindowFocus.ts | 3 ++- src-web/lib/fireAndForget.ts | 16 ++++++++++++++++ src-web/lib/initGlobalListeners.tsx | 9 +++++---- src-web/lib/theme/appearance.ts | 7 ++++--- src-web/tailwind.config.cjs | 1 + 44 files changed, 110 insertions(+), 75 deletions(-) create mode 100644 src-web/lib/fireAndForget.ts diff --git a/crates-tauri/yaak-license/index.ts b/crates-tauri/yaak-license/index.ts index 2a2fab8f..7de30646 100644 --- a/crates-tauri/yaak-license/index.ts +++ b/crates-tauri/yaak-license/index.ts @@ -29,7 +29,7 @@ export function useLicense() { await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }); }); return () => { - unlisten.then((fn) => fn()); + void unlisten.then((fn) => fn()); }; }, []); diff --git a/crates/yaak-git/index.ts b/crates/yaak-git/index.ts index b08d838b..559b1e5e 100644 --- a/crates/yaak-git/index.ts +++ b/crates/yaak-git/index.ts @@ -89,8 +89,8 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => { const handleError = (err: unknown) => { showToast({ - id: `${err}`, - message: `${err}`, + id: err instanceof Error ? err.message : String(err), + message: err instanceof Error ? err.message : String(err), color: 'danger', timeout: 5000, }); @@ -186,16 +186,16 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => { } if (result.type === 'uncommitted_changes') { - callbacks.promptUncommittedChanges().then(async (strategy) => { + void callbacks.promptUncommittedChanges().then(async (strategy) => { if (strategy === 'cancel') return; await invoke('cmd_git_reset_changes', { dir }); return invoke('cmd_git_pull', { dir }); - }).then(async () => { onSuccess(); await callbacks.forceSync(); }, handleError); + }).then(async () => { await onSuccess(); await callbacks.forceSync(); }, handleError); } if (result.type === 'diverged') { - callbacks.promptDiverged(result).then((strategy) => { + void callbacks.promptDiverged(result).then((strategy) => { if (strategy === 'cancel') return; if (strategy === 'force_reset') { @@ -211,7 +211,7 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => { remote: result.remote, branch: result.branch, }); - }).then(async () => { onSuccess(); await callbacks.forceSync(); }, handleError); + }).then(async () => { await onSuccess(); await callbacks.forceSync(); }, handleError); } return result; diff --git a/crates/yaak-sync/index.ts b/crates/yaak-sync/index.ts index 38f9e30e..295c03a6 100644 --- a/crates/yaak-sync/index.ts +++ b/crates/yaak-sync/index.ts @@ -39,7 +39,7 @@ export function watchWorkspaceFiles( channel, }); - unlistenPromise.then(({ unlistenEvent }) => { + void unlistenPromise.then(({ unlistenEvent }) => { addWatchKey(unlistenEvent); }); @@ -53,7 +53,7 @@ export function watchWorkspaceFiles( } function unlistenToWatcher(unlistenEvent: string) { - emit(unlistenEvent).then(() => { + void emit(unlistenEvent).then(() => { removeWatchKey(unlistenEvent); }); } diff --git a/packages/plugin-runtime/src/PluginInstance.ts b/packages/plugin-runtime/src/PluginInstance.ts index 451d648f..4516aae7 100644 --- a/packages/plugin-runtime/src/PluginInstance.ts +++ b/packages/plugin-runtime/src/PluginInstance.ts @@ -91,7 +91,7 @@ export class PluginInstance { ); } catch (err: unknown) { await ctx.toast.show({ - message: `Failed to initialize plugin ${this.#workerData.bootRequest.dir.split('/').pop()}: ${err}`, + message: `Failed to initialize plugin ${this.#workerData.bootRequest.dir.split('/').pop()}: ${err instanceof Error ? err.message : String(err)}`, color: 'notice', icon: 'alert_triangle', timeout: 30000, @@ -328,6 +328,7 @@ export class PluginInstance { payload.values = applyFormInputDefaults(args, payload.values); const resolvedArgs = await applyDynamicFormInput(ctx, args, payload); const resolvedActions: HttpAuthenticationAction[] = []; + // oxlint-disable-next-line unbound-method for (const { onSelect: _onSelect, ...action } of actions ?? []) { resolvedActions.push(action); } @@ -474,7 +475,7 @@ export class PluginInstance { { type: 'call_template_function_response', value: null, - error: `${err}`.replace(/^Error:\s*/g, ''), + error: (err instanceof Error ? err.message : String(err)).replace(/^Error:\s*/g, ''), }, replyId, ); @@ -483,7 +484,7 @@ export class PluginInstance { } } } catch (err) { - const error = `${err}`.replace(/^Error:\s*/g, ''); + const error = (err instanceof Error ? err.message : String(err)).replace(/^Error:\s*/g, ''); console.log('Plugin call threw exception', payload.type, '→', error); this.#sendPayload(context, { type: 'error_response', error }, replyId); return; diff --git a/packages/plugin-runtime/src/interceptStdout.ts b/packages/plugin-runtime/src/interceptStdout.ts index 940c1246..4729da31 100644 --- a/packages/plugin-runtime/src/interceptStdout.ts +++ b/packages/plugin-runtime/src/interceptStdout.ts @@ -1,3 +1,4 @@ +/* oxlint-disable unbound-method */ import process from 'node:process'; export function interceptStdout(intercept: (text: string) => string) { diff --git a/packages/plugin-runtime/tsconfig.json b/packages/plugin-runtime/tsconfig.json index 845d46fa..7b51fbe2 100644 --- a/packages/plugin-runtime/tsconfig.json +++ b/packages/plugin-runtime/tsconfig.json @@ -10,11 +10,7 @@ "moduleResolution": "node16", "resolveJsonModule": true, "sourceMap": true, - "outDir": "build", - "baseUrl": ".", - "paths": { - "*": ["node_modules/*", "src/types/*"] - } + "outDir": "build" }, "include": ["src"] } diff --git a/plugins-external/faker/tests/init.test.ts b/plugins-external/faker/tests/init.test.ts index 89d6a9f3..ca779769 100644 --- a/plugins-external/faker/tests/init.test.ts +++ b/plugins-external/faker/tests/init.test.ts @@ -13,6 +13,7 @@ describe('template-function-faker', () => { it('renders date results as unquoted ISO strings', async () => { const { plugin } = await import('../src/index'); const fn = plugin.templateFunctions?.find((fn) => fn.name === 'faker.date.future'); + // oxlint-disable-next-line unbound-method const onRender = fn?.onRender; expect(onRender).toBeTypeOf('function'); diff --git a/plugins-external/httpsnippet/src/index.ts b/plugins-external/httpsnippet/src/index.ts index 92303317..bc67d762 100644 --- a/plugins-external/httpsnippet/src/index.ts +++ b/plugins-external/httpsnippet/src/index.ts @@ -173,7 +173,7 @@ function toHarRequest(request: Partial) { return har; } -function maybeParseJSON(v: unknown, fallback: T): T | unknown { +function maybeParseJSON(v: unknown, fallback: T): unknown { if (typeof v !== 'string') return fallback; try { return JSON.parse(v); @@ -305,7 +305,7 @@ export const plugin: PluginDefinition = { }); } catch (err) { await ctx.toast.show({ - message: `Failed to generate snippet: ${err}`, + message: `Failed to generate snippet: ${err instanceof Error ? err.message : String(err)}`, icon: 'alert_triangle', color: 'danger', }); diff --git a/plugins-external/mcp-server/src/index.ts b/plugins-external/mcp-server/src/index.ts index 6561c47e..b92bfd57 100644 --- a/plugins-external/mcp-server/src/index.ts +++ b/plugins-external/mcp-server/src/index.ts @@ -15,7 +15,7 @@ export const plugin: PluginDefinition = { mcpServer = createMcpServer({ yaak: ctx }, serverPort); } catch (err) { console.error('Failed to start MCP server:', err); - ctx.toast.show({ + void ctx.toast.show({ message: `Failed to start MCP Server: ${err instanceof Error ? err.message : String(err)}`, icon: 'alert_triangle', color: 'danger', diff --git a/plugins-external/mcp-server/src/server.ts b/plugins-external/mcp-server/src/server.ts index e30fa76e..12989a3b 100644 --- a/plugins-external/mcp-server/src/server.ts +++ b/plugins-external/mcp-server/src/server.ts @@ -30,7 +30,7 @@ export function createMcpServer(ctx: McpServerContext, port: number) { if (!mcpServer.isConnected()) { // Connect the mcp with the transport await mcpServer.connect(transport); - ctx.yaak.toast.show({ + void ctx.yaak.toast.show({ message: `MCP Server connected`, icon: 'info', color: 'info', @@ -48,7 +48,7 @@ export function createMcpServer(ctx: McpServerContext, port: number) { }, (info) => { console.log('Started MCP server on ', info.address); - ctx.yaak.toast.show({ + void ctx.yaak.toast.show({ message: `MCP Server running on http://127.0.0.1:${info.port}`, icon: 'info', color: 'secondary', diff --git a/plugins/auth-oauth2/src/index.ts b/plugins/auth-oauth2/src/index.ts index 3dd55a2f..bf9f5f28 100644 --- a/plugins/auth-oauth2/src/index.ts +++ b/plugins/auth-oauth2/src/index.ts @@ -590,11 +590,11 @@ export const plugin: PluginDefinition = { credentialsInBody, }); } else { - throw new Error(`Invalid grant type ${grantType}`); + throw new Error(`Invalid grant type ${String(grantType)}`); } const headerName = stringArg(values, 'headerName') || 'Authorization'; - const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim(); + const headerValue = `${headerPrefix} ${token.response[tokenName] ?? ''}`.trim(); return { setHeaders: [{ name: headerName, value: headerValue }] }; }, }, diff --git a/plugins/filter-jsonpath/src/index.ts b/plugins/filter-jsonpath/src/index.ts index 551d442b..6a9f815e 100644 --- a/plugins/filter-jsonpath/src/index.ts +++ b/plugins/filter-jsonpath/src/index.ts @@ -11,7 +11,7 @@ export const plugin: PluginDefinition = { const filtered = JSONPath({ path: args.filter, json: parsed }); return { content: JSON.stringify(filtered, null, 2) }; } catch (err) { - return { content: '', error: `Invalid filter: ${err}` }; + return { content: '', error: `Invalid filter: ${err instanceof Error ? err.message : String(err)}` }; } }, }, diff --git a/plugins/filter-xpath/src/index.ts b/plugins/filter-xpath/src/index.ts index ebf632b9..091d2bb3 100644 --- a/plugins/filter-xpath/src/index.ts +++ b/plugins/filter-xpath/src/index.ts @@ -18,7 +18,7 @@ export const plugin: PluginDefinition = { // Not sure what cases this happens in (?) return { content: String(result) }; } catch (err) { - return { content: '', error: `Invalid filter: ${err}` }; + return { content: '', error: `Invalid filter: ${err instanceof Error ? err.message : String(err)}` }; } }, }, diff --git a/plugins/importer-insomnia/src/v4.ts b/plugins/importer-insomnia/src/v4.ts index 4785c281..412cce80 100644 --- a/plugins/importer-insomnia/src/v4.ts +++ b/plugins/importer-insomnia/src/v4.ts @@ -203,7 +203,7 @@ function importEnvironment( variables: Object.entries(e.data).map(([name, value]) => ({ enabled: true, name, - value: `${value}`, + value: String(value), })), }; } diff --git a/plugins/importer-insomnia/src/v5.ts b/plugins/importer-insomnia/src/v5.ts index b22b6f0f..a8434a2c 100644 --- a/plugins/importer-insomnia/src/v5.ts +++ b/plugins/importer-insomnia/src/v5.ts @@ -261,7 +261,7 @@ function importFolder( variables: Object.entries(f.environment ?? {}).map(([name, value]) => ({ enabled: true, name, - value: `${value}`, + value: String(value), })), }; } @@ -308,7 +308,7 @@ function importEnvironment( variables: Object.entries(e.data ?? {}).map(([name, value]) => ({ enabled: true, name, - value: `${value}`, + value: String(value), })), }; } diff --git a/plugins/importer-postman-environment/src/index.ts b/plugins/importer-postman-environment/src/index.ts index 3a47daa1..8cb31764 100644 --- a/plugins/importer-postman-environment/src/index.ts +++ b/plugins/importer-postman-environment/src/index.ts @@ -85,7 +85,7 @@ function parseJSONToRecord(jsonStr: string): Record | null { } } -function toRecord(value: Record | unknown): Record { +function toRecord(value: unknown): Record { if (value && typeof value === 'object' && !Array.isArray(value)) { return value as Record; } diff --git a/plugins/importer-postman/src/index.ts b/plugins/importer-postman/src/index.ts index df2cdc87..68f4a2d8 100644 --- a/plugins/importer-postman/src/index.ts +++ b/plugins/importer-postman/src/index.ts @@ -159,7 +159,7 @@ export function convertPostman(contents: string): ImportPluginResponse | undefin return { resources }; } -function convertUrl(rawUrl: string | unknown): Pick { +function convertUrl(rawUrl: unknown): Pick { if (typeof rawUrl === 'string') { return { url: rawUrl, urlParameters: [] }; } @@ -173,7 +173,7 @@ function convertUrl(rawUrl: string | unknown): Pick(jsonStr: string): Record | null { } } -function toRecord(value: Record | unknown): Record { +function toRecord(value: unknown): Record { if (value && typeof value === 'object' && !Array.isArray(value)) { return value as Record; } diff --git a/src-web/commands/openWorkspaceFromSyncDir.tsx b/src-web/commands/openWorkspaceFromSyncDir.tsx index 83426ce9..1c1f10e4 100644 --- a/src-web/commands/openWorkspaceFromSyncDir.tsx +++ b/src-web/commands/openWorkspaceFromSyncDir.tsx @@ -19,7 +19,7 @@ export const openWorkspaceFromSyncDir = createFastMutation({ await applySync(workspace.id, dir, ops); - router.navigate({ + await router.navigate({ to: '/workspaces/$workspaceId', params: { workspaceId: workspace.id }, }); diff --git a/src-web/components/DnsOverridesEditor.tsx b/src-web/components/DnsOverridesEditor.tsx index 119ce8a5..41530ece 100644 --- a/src-web/components/DnsOverridesEditor.tsx +++ b/src-web/components/DnsOverridesEditor.tsx @@ -1,6 +1,7 @@ import type { DnsOverride, Workspace } from '@yaakapp-internal/models'; import { patchModel } from '@yaakapp-internal/models'; import { useCallback, useId, useMemo } from 'react'; +import { fireAndForget } from '../lib/fireAndForget'; import { Button } from './core/Button'; import { Checkbox } from './core/Checkbox'; import { IconButton } from './core/IconButton'; @@ -29,7 +30,7 @@ export function DnsOverridesEditor({ workspace }: Props) { const handleChange = useCallback( (overrides: DnsOverride[]) => { - patchModel(workspace, { settingDnsOverrides: overrides }); + fireAndForget(patchModel(workspace, { settingDnsOverrides: overrides })); }, [workspace], ); diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx index ffaca646..b9104b9a 100644 --- a/src-web/components/EnvironmentEditDialog.tsx +++ b/src-web/components/EnvironmentEditDialog.tsx @@ -9,6 +9,7 @@ import { useEnvironmentsBreakdown, } from '../hooks/useEnvironmentsBreakdown'; import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm'; +import { fireAndForget } from '../lib/fireAndForget'; import { jotaiStore } from '../lib/jotai'; import { isBaseEnvironment, isSubEnvironment } from '../lib/model_util'; import { resolvedModelName } from '../lib/resolvedModelName'; @@ -199,7 +200,7 @@ function EnvironmentEditDialogSidebar({ // Not sure why this is needed, but without it the // edit input blurs immediately after opening. requestAnimationFrame(() => { - actions['sidebar.selected.rename'].cb(items); + fireAndForget(actions['sidebar.selected.rename'].cb(items)); }); }, }, diff --git a/src-web/components/FolderLayout.tsx b/src-web/components/FolderLayout.tsx index e3bf3947..8609d2a8 100644 --- a/src-web/components/FolderLayout.tsx +++ b/src-web/components/FolderLayout.tsx @@ -8,6 +8,7 @@ import { allRequestsAtom } from '../hooks/useAllRequests'; import { useFolderActions } from '../hooks/useFolderActions'; import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse'; import { sendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; +import { fireAndForget } from '../lib/fireAndForget'; import { showDialog } from '../lib/dialog'; import { resolvedModelName } from '../lib/resolvedModelName'; import { router } from '../lib/router'; @@ -45,7 +46,7 @@ export function FolderLayout({ folder, style }: Props) { }, [folder.id, folders, requests]); const handleSendAll = useCallback(() => { - sendAllAction?.call(folder); + if (sendAllAction) fireAndForget(sendAllAction.call(folder)); }, [sendAllAction, folder]); return ( diff --git a/src-web/components/ImportCurlButton.tsx b/src-web/components/ImportCurlButton.tsx index 5d40fc3f..c495b5c0 100644 --- a/src-web/components/ImportCurlButton.tsx +++ b/src-web/components/ImportCurlButton.tsx @@ -1,6 +1,7 @@ import { clear, readText } from '@tauri-apps/plugin-clipboard-manager'; import * as m from 'motion/react-m'; import { useEffect, useState } from 'react'; +import { fireAndForget } from '../lib/fireAndForget'; import { useImportCurl } from '../hooks/useImportCurl'; import { useWindowFocus } from '../hooks/useWindowFocus'; import { Button } from './core/Button'; @@ -15,7 +16,7 @@ export function ImportCurlButton() { // biome-ignore lint/correctness/useExhaustiveDependencies: none useEffect(() => { - readText().then(setClipboardText); + fireAndForget(readText().then(setClipboardText)); }, [focused]); if (!clipboardText?.trim().startsWith('curl ')) { diff --git a/src-web/components/JsonBodyEditor.tsx b/src-web/components/JsonBodyEditor.tsx index ab33f019..e23b0f7c 100644 --- a/src-web/components/JsonBodyEditor.tsx +++ b/src-web/components/JsonBodyEditor.tsx @@ -2,6 +2,7 @@ import { linter } from '@codemirror/lint'; import type { HttpRequest } from '@yaakapp-internal/models'; import { patchModel } from '@yaakapp-internal/models'; import { useCallback, useMemo } from 'react'; +import { fireAndForget } from '../lib/fireAndForget'; import { useKeyValue } from '../hooks/useKeyValue'; import { textLikelyContainsJsonComments } from '../lib/jsonComments'; import { Banner } from './core/Banner'; @@ -58,12 +59,12 @@ export function JsonBodyEditor({ forceUpdateKey, heightMode, request }: Props) { } else { delete newBody.sendJsonComments; } - patchModel(request, { body: newBody }); + fireAndForget(patchModel(request, { body: newBody })); }, [request, autoFix]); const handleDropdownOpen = useCallback(() => { if (!bannerDismissed) { - setBannerDismissed(true); + fireAndForget(setBannerDismissed(true)); } }, [bannerDismissed, setBannerDismissed]); diff --git a/src-web/components/RedirectToLatestWorkspace.tsx b/src-web/components/RedirectToLatestWorkspace.tsx index 9c36231b..2202e12d 100644 --- a/src-web/components/RedirectToLatestWorkspace.tsx +++ b/src-web/components/RedirectToLatestWorkspace.tsx @@ -5,6 +5,7 @@ import { getRecentCookieJars } from '../hooks/useRecentCookieJars'; import { getRecentEnvironments } from '../hooks/useRecentEnvironments'; import { getRecentRequests } from '../hooks/useRecentRequests'; import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces'; +import { fireAndForget } from '../lib/fireAndForget'; import { router } from '../lib/router'; export function RedirectToLatestWorkspace() { @@ -20,7 +21,7 @@ export function RedirectToLatestWorkspace() { return; } - (async () => { + fireAndForget((async () => { const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? 'n/a'; const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null; const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null; @@ -34,7 +35,7 @@ export function RedirectToLatestWorkspace() { console.log('Redirecting to workspace', params, search); await router.navigate({ to: '/workspaces/$workspaceId', params, search }); - })(); + })()); }, [recentWorkspaces, workspaces, workspaces.length]); return null; diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index c7b8aec3..749875ad 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -43,6 +43,7 @@ import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { getWebsocketRequestActions } from '../hooks/useWebsocketRequestActions'; import { deepEqualAtom } from '../lib/atoms'; import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm'; +import { fireAndForget } from '../lib/fireAndForget'; import { jotaiStore } from '../lib/jotai'; import { resolvedModelName } from '../lib/resolvedModelName'; import { isSidebarFocused } from '../lib/scopes'; @@ -439,7 +440,7 @@ function Sidebar({ className }: { className?: string }) { leftSlot: , hidden: workspaces.length <= 1 || requestItems.length === 0 || requestItems.length !== items.length, onSelect: () => { - actions['sidebar.selected.move'].cb(items); + fireAndForget(actions['sidebar.selected.move'].cb(items)); }, }, { diff --git a/src-web/components/WorkspaceEncryptionSetting.tsx b/src-web/components/WorkspaceEncryptionSetting.tsx index 25bd1136..b55e9dd7 100644 --- a/src-web/components/WorkspaceEncryptionSetting.tsx +++ b/src-web/components/WorkspaceEncryptionSetting.tsx @@ -127,7 +127,7 @@ export function WorkspaceEncryptionSetting({ size, expanded, onDone, onEnabledEn await enableEncryption(workspaceMeta.workspaceId); setJustEnabledEncryption(true); } catch (err) { - setError(`Failed to enable encryption: ${err}`); + setError(`Failed to enable encryption: ${err instanceof Error ? err.message : String(err)}`); } }} > diff --git a/src-web/components/core/Dropdown.tsx b/src-web/components/core/Dropdown.tsx index 22b9f074..a7534b43 100644 --- a/src-web/components/core/Dropdown.tsx +++ b/src-web/components/core/Dropdown.tsx @@ -25,6 +25,7 @@ import { } from 'react'; import { useKey, useWindowSize } from 'react-use'; import { useClickOutside } from '../../hooks/useClickOutside'; +import { fireAndForget } from '../../lib/fireAndForget'; import type { HotkeyAction } from '../../hooks/useHotKey'; import { useHotKey } from '../../hooks/useHotKey'; import { useStateWithDeps } from '../../hooks/useStateWithDeps'; @@ -614,7 +615,7 @@ const Menu = forwardRef(function Tabs( forwardedRef, () => ({ setActiveTab: (value: string) => { - onChangeValue(value); + fireAndForget(onChangeValue(value)); }, }), [onChangeValue], diff --git a/src-web/components/git/GitDropdown.tsx b/src-web/components/git/GitDropdown.tsx index 210c8452..2c3667e8 100644 --- a/src-web/components/git/GitDropdown.tsx +++ b/src-web/components/git/GitDropdown.tsx @@ -11,6 +11,7 @@ import { useRandomKey } from '../../hooks/useRandomKey'; import { sync } from '../../init/sync'; import { showConfirm, showConfirmDelete } from '../../lib/confirm'; import { showDialog } from '../../lib/dialog'; +import { fireAndForget } from '../../lib/fireAndForget'; import { showPrompt } from '../../lib/prompt'; import { showErrorToast, showToast } from '../../lib/toast'; import { Banner } from '../core/Banner'; @@ -246,7 +247,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) { message: 'Changes have been reset', color: 'success', }); - sync({ force: true }); + fireAndForget(sync({ force: true })); }, onError(err) { showErrorToast({ @@ -293,7 +294,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) { ), }); - sync({ force: true }); + fireAndForget(sync({ force: true })); }, onError(err) { showErrorToast({ diff --git a/src-web/components/graphql/GraphQLDocsExplorer.tsx b/src-web/components/graphql/GraphQLDocsExplorer.tsx index dfc1a793..952d51fa 100644 --- a/src-web/components/graphql/GraphQLDocsExplorer.tsx +++ b/src-web/components/graphql/GraphQLDocsExplorer.tsx @@ -270,7 +270,7 @@ function GqlTypeInfo({ {Object.entries(fields).map(([fieldName, field]) => { const fieldItem: ExplorerItem = toExplorerItem(field, item); return ( -
+
Arguments {item.type.args.map((a) => { return ( -
+
+
+
( {item.type.args.map((arg) => (
{item.type.args.length > 1 && <>  } diff --git a/src-web/components/responseViewers/PdfViewer.tsx b/src-web/components/responseViewers/PdfViewer.tsx index 009cd637..7b0b236a 100644 --- a/src-web/components/responseViewers/PdfViewer.tsx +++ b/src-web/components/responseViewers/PdfViewer.tsx @@ -6,13 +6,14 @@ import type { PDFDocumentProxy } from 'pdfjs-dist'; import { useEffect, useRef, useState } from 'react'; import { Document, Page } from 'react-pdf'; import { useContainerSize } from '../../hooks/useContainerQuery'; +import { fireAndForget } from '../../lib/fireAndForget'; -import('react-pdf').then(({ pdfjs }) => { +fireAndForget(import('react-pdf').then(({ pdfjs }) => { pdfjs.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url, ).toString(); -}); +})); interface Props { bodyPath?: string; diff --git a/src-web/font-size.ts b/src-web/font-size.ts index 2e52ddc1..868b5fe3 100644 --- a/src-web/font-size.ts +++ b/src-web/font-size.ts @@ -1,6 +1,7 @@ // Listen for settings changes, the re-compute theme import { listen } from '@tauri-apps/api/event'; import type { ModelPayload } from '@yaakapp-internal/models'; +import { fireAndForget } from './lib/fireAndForget'; import { getSettings } from './lib/settings'; function setFontSizeOnDocument(fontSize: number) { @@ -13,4 +14,4 @@ listen('model_write', async (event) => { setFontSizeOnDocument(event.payload.model.interfaceFontSize); }).catch(console.error); -getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize)); +fireAndForget(getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize))); diff --git a/src-web/font.ts b/src-web/font.ts index e02c65e2..7be7985f 100644 --- a/src-web/font.ts +++ b/src-web/font.ts @@ -1,6 +1,7 @@ // Listen for settings changes, the re-compute theme import { listen } from '@tauri-apps/api/event'; import type { ModelPayload, Settings } from '@yaakapp-internal/models'; +import { fireAndForget } from './lib/fireAndForget'; import { getSettings } from './lib/settings'; function setFonts(settings: Settings) { @@ -17,4 +18,4 @@ listen('model_write', async (event) => { setFonts(event.payload.model); }).catch(console.error); -getSettings().then((settings) => setFonts(settings)); +fireAndForget(getSettings().then((settings) => setFonts(settings))); diff --git a/src-web/hooks/useActiveRequest.ts b/src-web/hooks/useActiveRequest.ts index 26e647bf..84a5436f 100644 --- a/src-web/hooks/useActiveRequest.ts +++ b/src-web/hooks/useActiveRequest.ts @@ -16,7 +16,7 @@ interface TypeMap { } export function useActiveRequest( - model?: T | undefined, + model?: T, ): TypeMap[T] | null { const activeRequest = useAtomValue(activeRequestAtom); if (model == null) return activeRequest as TypeMap[T]; diff --git a/src-web/hooks/useCreateDropdownItems.tsx b/src-web/hooks/useCreateDropdownItems.tsx index 04776a55..226ecb15 100644 --- a/src-web/hooks/useCreateDropdownItems.tsx +++ b/src-web/hooks/useCreateDropdownItems.tsx @@ -18,7 +18,7 @@ export function useCreateDropdownItems({ }: { hideFolder?: boolean; hideIcons?: boolean; - folderId?: string | null | 'active-folder'; + folderId?: string | null; } = {}): DropdownItem[] { const workspaceId = useAtomValue(activeWorkspaceIdAtom); const activeRequest = useAtomValue(activeRequestAtom); @@ -40,7 +40,7 @@ export function getCreateDropdownItems({ }: { hideFolder?: boolean; hideIcons?: boolean; - folderId?: string | null | 'active-folder'; + folderId?: string | null; workspaceId: string | null; activeRequest: HttpRequest | GrpcRequest | WebsocketRequest | null; onCreate?: ( diff --git a/src-web/hooks/useFastMutation.ts b/src-web/hooks/useFastMutation.ts index 1719c2cb..498a0bb8 100644 --- a/src-web/hooks/useFastMutation.ts +++ b/src-web/hooks/useFastMutation.ts @@ -42,7 +42,7 @@ export function createFastMutation('cmd_get_http_response_events', { responseId: response.id }).then( + fireAndForget(invoke('cmd_get_http_response_events', { responseId: response.id }).then( (events) => mergeModelsInStore('http_response_event', events, (e) => e.responseId === response.id), - ); + )); }, [response?.id]); const events = allEvents.filter((e) => e.responseId === response?.id); diff --git a/src-web/hooks/usePinnedGrpcConnection.ts b/src-web/hooks/usePinnedGrpcConnection.ts index 8e655d33..7f29c67f 100644 --- a/src-web/hooks/usePinnedGrpcConnection.ts +++ b/src-web/hooks/usePinnedGrpcConnection.ts @@ -8,6 +8,7 @@ import { } from '@yaakapp-internal/models'; import { atom, useAtomValue } from 'jotai'; import { useEffect, useMemo } from 'react'; +import { fireAndForget } from '../lib/fireAndForget'; import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage'; import { activeRequestIdAtom } from './useActiveRequestId'; @@ -69,9 +70,9 @@ export function useGrpcEvents(connectionId: string | null) { } // Fetch events from database, filtering out events from other connections and merging atomically - invoke('models_grpc_events', { connectionId }).then((events) => + fireAndForget(invoke('models_grpc_events', { connectionId }).then((events) => mergeModelsInStore('grpc_event', events, (e) => e.connectionId === connectionId), - ); + )); }, [connectionId]); return useMemo( diff --git a/src-web/hooks/usePinnedWebsocketConnection.ts b/src-web/hooks/usePinnedWebsocketConnection.ts index a1a4dceb..11484f83 100644 --- a/src-web/hooks/usePinnedWebsocketConnection.ts +++ b/src-web/hooks/usePinnedWebsocketConnection.ts @@ -8,6 +8,7 @@ import { } from '@yaakapp-internal/models'; import { atom, useAtomValue } from 'jotai'; import { useEffect, useMemo } from 'react'; +import { fireAndForget } from '../lib/fireAndForget'; import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage'; import { jotaiStore } from '../lib/jotai'; import { activeRequestIdAtom } from './useActiveRequestId'; @@ -56,9 +57,9 @@ export function useWebsocketEvents(connectionId: string | null) { } // Fetch events from database, filtering out events from other connections and merging atomically - invoke('models_websocket_events', { connectionId }).then((events) => + fireAndForget(invoke('models_websocket_events', { connectionId }).then((events) => mergeModelsInStore('websocket_event', events, (e) => e.connectionId === connectionId), - ); + )); }, [connectionId]); return useMemo( diff --git a/src-web/hooks/useWindowFocus.ts b/src-web/hooks/useWindowFocus.ts index f5f2831e..129cb96f 100644 --- a/src-web/hooks/useWindowFocus.ts +++ b/src-web/hooks/useWindowFocus.ts @@ -1,5 +1,6 @@ import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { useEffect, useState } from 'react'; +import { fireAndForget } from '../lib/fireAndForget'; export function useWindowFocus() { const [visible, setVisible] = useState(true); @@ -10,7 +11,7 @@ export function useWindowFocus() { }); return () => { - unlisten.then((fn) => fn()); + fireAndForget(unlisten.then((fn) => fn())); }; }, []); diff --git a/src-web/lib/fireAndForget.ts b/src-web/lib/fireAndForget.ts new file mode 100644 index 00000000..4ce19553 --- /dev/null +++ b/src-web/lib/fireAndForget.ts @@ -0,0 +1,16 @@ +import { showErrorToast } from './toast'; + +/** + * Handles a fire-and-forget promise by catching and reporting errors + * via console.error and a toast notification. + */ +export function fireAndForget(promise: Promise) { + promise.catch((err: unknown) => { + console.error('Unhandled async error:', err); + showErrorToast({ + id: 'async-error', + title: 'Unexpected Error', + message: err instanceof Error ? err.message : String(err), + }); + }); +} diff --git a/src-web/lib/initGlobalListeners.tsx b/src-web/lib/initGlobalListeners.tsx index 1df5d723..53636691 100644 --- a/src-web/lib/initGlobalListeners.tsx +++ b/src-web/lib/initGlobalListeners.tsx @@ -22,6 +22,7 @@ import { HStack, VStack } from '../components/core/Stacks'; // Listen for toasts import { listenToTauriEvent } from '../hooks/useListenToTauriEvent'; +import { fireAndForget } from './fireAndForget'; import { updateAvailableAtom } from './atoms'; import { stringToColor } from './color'; import { generateId } from './generateId'; @@ -81,7 +82,7 @@ export function initGlobalListeners() { done, }, }; - emit(event.id, result); + fireAndForget(emit(event.id, result)); }; const values = await showPromptForm({ @@ -110,7 +111,7 @@ export function initGlobalListeners() { // Listen for update events listenToTauriEvent('update_available', async ({ payload }) => { console.log('Got update available', payload); - showUpdateAvailableToast(payload); + fireAndForget(showUpdateAvailableToast(payload)); }); listenToTauriEvent('notification', ({ payload }) => { @@ -125,7 +126,7 @@ export function initGlobalListeners() { }); // Check for plugin initialization errors - invokeCmd<[string, string][]>('cmd_plugin_init_errors').then((errors) => { + fireAndForget(invokeCmd<[string, string][]>('cmd_plugin_init_errors').then((errors) => { for (const [dir, message] of errors) { const dirBasename = dir.split('/').pop() ?? dir; showToast({ @@ -155,7 +156,7 @@ export function initGlobalListeners() { ), }); } - }); + })); } function showUpdateInstalledToast(version: string) { diff --git a/src-web/lib/theme/appearance.ts b/src-web/lib/theme/appearance.ts index 4d199e90..c271ddf5 100644 --- a/src-web/lib/theme/appearance.ts +++ b/src-web/lib/theme/appearance.ts @@ -1,4 +1,5 @@ import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; +import { fireAndForget } from '../fireAndForget'; export type Appearance = 'light' | 'dark'; @@ -22,13 +23,13 @@ export function subscribeToWindowAppearanceChange( unsubscribe: () => {}, }; - getCurrentWebviewWindow() + fireAndForget(getCurrentWebviewWindow() .onThemeChanged((t) => { cb(t.payload); }) .then((l) => { container.unsubscribe = l; - }); + })); return () => container.unsubscribe(); } @@ -43,6 +44,6 @@ export function resolveAppearance( export function subscribeToPreferredAppearance(cb: (a: Appearance) => void) { cb(getCSSAppearance()); - getWindowAppearance().then(cb); + fireAndForget(getWindowAppearance().then(cb)); subscribeToWindowAppearanceChange(cb); } diff --git a/src-web/tailwind.config.cjs b/src-web/tailwind.config.cjs index e6423f61..dea60b8e 100644 --- a/src-web/tailwind.config.cjs +++ b/src-web/tailwind.config.cjs @@ -138,6 +138,7 @@ module.exports = { }, plugins: [ require('@tailwindcss/container-queries'), + // oxlint-disable-next-line unbound-method plugin(function ({ addVariant }) { addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']); addVariant('focus-visible-or-class', ['&:focus-visible', '&.focus:focus']);