Fix lint errors

This commit is contained in:
Gregory Schier
2026-03-13 08:12:21 -07:00
parent be34dfe74a
commit 7670ab007f
44 changed files with 110 additions and 75 deletions

View File

@@ -29,7 +29,7 @@ export function useLicense() {
await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }); await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY });
}); });
return () => { return () => {
unlisten.then((fn) => fn()); void unlisten.then((fn) => fn());
}; };
}, []); }, []);

View File

@@ -89,8 +89,8 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
const handleError = (err: unknown) => { const handleError = (err: unknown) => {
showToast({ showToast({
id: `${err}`, id: err instanceof Error ? err.message : String(err),
message: `${err}`, message: err instanceof Error ? err.message : String(err),
color: 'danger', color: 'danger',
timeout: 5000, timeout: 5000,
}); });
@@ -186,16 +186,16 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
} }
if (result.type === 'uncommitted_changes') { if (result.type === 'uncommitted_changes') {
callbacks.promptUncommittedChanges().then(async (strategy) => { void callbacks.promptUncommittedChanges().then(async (strategy) => {
if (strategy === 'cancel') return; if (strategy === 'cancel') return;
await invoke('cmd_git_reset_changes', { dir }); await invoke('cmd_git_reset_changes', { dir });
return invoke<PullResult>('cmd_git_pull', { dir }); return invoke<PullResult>('cmd_git_pull', { dir });
}).then(async () => { onSuccess(); await callbacks.forceSync(); }, handleError); }).then(async () => { await onSuccess(); await callbacks.forceSync(); }, handleError);
} }
if (result.type === 'diverged') { if (result.type === 'diverged') {
callbacks.promptDiverged(result).then((strategy) => { void callbacks.promptDiverged(result).then((strategy) => {
if (strategy === 'cancel') return; if (strategy === 'cancel') return;
if (strategy === 'force_reset') { if (strategy === 'force_reset') {
@@ -211,7 +211,7 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
remote: result.remote, remote: result.remote,
branch: result.branch, branch: result.branch,
}); });
}).then(async () => { onSuccess(); await callbacks.forceSync(); }, handleError); }).then(async () => { await onSuccess(); await callbacks.forceSync(); }, handleError);
} }
return result; return result;

View File

@@ -39,7 +39,7 @@ export function watchWorkspaceFiles(
channel, channel,
}); });
unlistenPromise.then(({ unlistenEvent }) => { void unlistenPromise.then(({ unlistenEvent }) => {
addWatchKey(unlistenEvent); addWatchKey(unlistenEvent);
}); });
@@ -53,7 +53,7 @@ export function watchWorkspaceFiles(
} }
function unlistenToWatcher(unlistenEvent: string) { function unlistenToWatcher(unlistenEvent: string) {
emit(unlistenEvent).then(() => { void emit(unlistenEvent).then(() => {
removeWatchKey(unlistenEvent); removeWatchKey(unlistenEvent);
}); });
} }

View File

@@ -91,7 +91,7 @@ export class PluginInstance {
); );
} catch (err: unknown) { } catch (err: unknown) {
await ctx.toast.show({ 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', color: 'notice',
icon: 'alert_triangle', icon: 'alert_triangle',
timeout: 30000, timeout: 30000,
@@ -328,6 +328,7 @@ export class PluginInstance {
payload.values = applyFormInputDefaults(args, payload.values); payload.values = applyFormInputDefaults(args, payload.values);
const resolvedArgs = await applyDynamicFormInput(ctx, args, payload); const resolvedArgs = await applyDynamicFormInput(ctx, args, payload);
const resolvedActions: HttpAuthenticationAction[] = []; const resolvedActions: HttpAuthenticationAction[] = [];
// oxlint-disable-next-line unbound-method
for (const { onSelect: _onSelect, ...action } of actions ?? []) { for (const { onSelect: _onSelect, ...action } of actions ?? []) {
resolvedActions.push(action); resolvedActions.push(action);
} }
@@ -474,7 +475,7 @@ export class PluginInstance {
{ {
type: 'call_template_function_response', type: 'call_template_function_response',
value: null, value: null,
error: `${err}`.replace(/^Error:\s*/g, ''), error: (err instanceof Error ? err.message : String(err)).replace(/^Error:\s*/g, ''),
}, },
replyId, replyId,
); );
@@ -483,7 +484,7 @@ export class PluginInstance {
} }
} }
} catch (err) { } 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); console.log('Plugin call threw exception', payload.type, '→', error);
this.#sendPayload(context, { type: 'error_response', error }, replyId); this.#sendPayload(context, { type: 'error_response', error }, replyId);
return; return;

View File

@@ -1,3 +1,4 @@
/* oxlint-disable unbound-method */
import process from 'node:process'; import process from 'node:process';
export function interceptStdout(intercept: (text: string) => string) { export function interceptStdout(intercept: (text: string) => string) {

View File

@@ -10,11 +10,7 @@
"moduleResolution": "node16", "moduleResolution": "node16",
"resolveJsonModule": true, "resolveJsonModule": true,
"sourceMap": true, "sourceMap": true,
"outDir": "build", "outDir": "build"
"baseUrl": ".",
"paths": {
"*": ["node_modules/*", "src/types/*"]
}
}, },
"include": ["src"] "include": ["src"]
} }

View File

@@ -13,6 +13,7 @@ describe('template-function-faker', () => {
it('renders date results as unquoted ISO strings', async () => { it('renders date results as unquoted ISO strings', async () => {
const { plugin } = await import('../src/index'); const { plugin } = await import('../src/index');
const fn = plugin.templateFunctions?.find((fn) => fn.name === 'faker.date.future'); const fn = plugin.templateFunctions?.find((fn) => fn.name === 'faker.date.future');
// oxlint-disable-next-line unbound-method
const onRender = fn?.onRender; const onRender = fn?.onRender;
expect(onRender).toBeTypeOf('function'); expect(onRender).toBeTypeOf('function');

View File

@@ -173,7 +173,7 @@ function toHarRequest(request: Partial<HttpRequest>) {
return har; return har;
} }
function maybeParseJSON<T>(v: unknown, fallback: T): T | unknown { function maybeParseJSON<T>(v: unknown, fallback: T): unknown {
if (typeof v !== 'string') return fallback; if (typeof v !== 'string') return fallback;
try { try {
return JSON.parse(v); return JSON.parse(v);
@@ -305,7 +305,7 @@ export const plugin: PluginDefinition = {
}); });
} catch (err) { } catch (err) {
await ctx.toast.show({ 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', icon: 'alert_triangle',
color: 'danger', color: 'danger',
}); });

View File

@@ -15,7 +15,7 @@ export const plugin: PluginDefinition = {
mcpServer = createMcpServer({ yaak: ctx }, serverPort); mcpServer = createMcpServer({ yaak: ctx }, serverPort);
} catch (err) { } catch (err) {
console.error('Failed to start MCP server:', 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)}`, message: `Failed to start MCP Server: ${err instanceof Error ? err.message : String(err)}`,
icon: 'alert_triangle', icon: 'alert_triangle',
color: 'danger', color: 'danger',

View File

@@ -30,7 +30,7 @@ export function createMcpServer(ctx: McpServerContext, port: number) {
if (!mcpServer.isConnected()) { if (!mcpServer.isConnected()) {
// Connect the mcp with the transport // Connect the mcp with the transport
await mcpServer.connect(transport); await mcpServer.connect(transport);
ctx.yaak.toast.show({ void ctx.yaak.toast.show({
message: `MCP Server connected`, message: `MCP Server connected`,
icon: 'info', icon: 'info',
color: 'info', color: 'info',
@@ -48,7 +48,7 @@ export function createMcpServer(ctx: McpServerContext, port: number) {
}, },
(info) => { (info) => {
console.log('Started MCP server on ', info.address); 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}`, message: `MCP Server running on http://127.0.0.1:${info.port}`,
icon: 'info', icon: 'info',
color: 'secondary', color: 'secondary',

View File

@@ -590,11 +590,11 @@ export const plugin: PluginDefinition = {
credentialsInBody, credentialsInBody,
}); });
} else { } else {
throw new Error(`Invalid grant type ${grantType}`); throw new Error(`Invalid grant type ${String(grantType)}`);
} }
const headerName = stringArg(values, 'headerName') || 'Authorization'; 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 }] }; return { setHeaders: [{ name: headerName, value: headerValue }] };
}, },
}, },

View File

@@ -11,7 +11,7 @@ export const plugin: PluginDefinition = {
const filtered = JSONPath({ path: args.filter, json: parsed }); const filtered = JSONPath({ path: args.filter, json: parsed });
return { content: JSON.stringify(filtered, null, 2) }; return { content: JSON.stringify(filtered, null, 2) };
} catch (err) { } catch (err) {
return { content: '', error: `Invalid filter: ${err}` }; return { content: '', error: `Invalid filter: ${err instanceof Error ? err.message : String(err)}` };
} }
}, },
}, },

View File

@@ -18,7 +18,7 @@ export const plugin: PluginDefinition = {
// Not sure what cases this happens in (?) // Not sure what cases this happens in (?)
return { content: String(result) }; return { content: String(result) };
} catch (err) { } catch (err) {
return { content: '', error: `Invalid filter: ${err}` }; return { content: '', error: `Invalid filter: ${err instanceof Error ? err.message : String(err)}` };
} }
}, },
}, },

View File

@@ -203,7 +203,7 @@ function importEnvironment(
variables: Object.entries(e.data).map(([name, value]) => ({ variables: Object.entries(e.data).map(([name, value]) => ({
enabled: true, enabled: true,
name, name,
value: `${value}`, value: String(value),
})), })),
}; };
} }

View File

@@ -261,7 +261,7 @@ function importFolder(
variables: Object.entries(f.environment ?? {}).map(([name, value]) => ({ variables: Object.entries(f.environment ?? {}).map(([name, value]) => ({
enabled: true, enabled: true,
name, name,
value: `${value}`, value: String(value),
})), })),
}; };
} }
@@ -308,7 +308,7 @@ function importEnvironment(
variables: Object.entries(e.data ?? {}).map(([name, value]) => ({ variables: Object.entries(e.data ?? {}).map(([name, value]) => ({
enabled: true, enabled: true,
name, name,
value: `${value}`, value: String(value),
})), })),
}; };
} }

View File

@@ -85,7 +85,7 @@ function parseJSONToRecord<T>(jsonStr: string): Record<string, T> | null {
} }
} }
function toRecord<T>(value: Record<string, T> | unknown): Record<string, T> { function toRecord<T>(value: unknown): Record<string, T> {
if (value && typeof value === 'object' && !Array.isArray(value)) { if (value && typeof value === 'object' && !Array.isArray(value)) {
return value as Record<string, T>; return value as Record<string, T>;
} }

View File

@@ -159,7 +159,7 @@ export function convertPostman(contents: string): ImportPluginResponse | undefin
return { resources }; return { resources };
} }
function convertUrl(rawUrl: string | unknown): Pick<HttpRequest, 'url' | 'urlParameters'> { function convertUrl(rawUrl: unknown): Pick<HttpRequest, 'url' | 'urlParameters'> {
if (typeof rawUrl === 'string') { if (typeof rawUrl === 'string') {
return { url: rawUrl, urlParameters: [] }; return { url: rawUrl, urlParameters: [] };
} }
@@ -173,7 +173,7 @@ function convertUrl(rawUrl: string | unknown): Pick<HttpRequest, 'url' | 'urlPar
} }
if ('host' in url) { if ('host' in url) {
v += `${Array.isArray(url.host) ? url.host.join('.') : url.host}`; v += `${Array.isArray(url.host) ? url.host.join('.') : String(url.host)}`;
} }
if ('port' in url && typeof url.port === 'string') { if ('port' in url && typeof url.port === 'string') {
@@ -490,7 +490,7 @@ function parseJSONToRecord<T>(jsonStr: string): Record<string, T> | null {
} }
} }
function toRecord<T>(value: Record<string, T> | unknown): Record<string, T> { function toRecord<T>(value: unknown): Record<string, T> {
if (value && typeof value === 'object' && !Array.isArray(value)) { if (value && typeof value === 'object' && !Array.isArray(value)) {
return value as Record<string, T>; return value as Record<string, T>;
} }

View File

@@ -19,7 +19,7 @@ export const openWorkspaceFromSyncDir = createFastMutation<void, void, string>({
await applySync(workspace.id, dir, ops); await applySync(workspace.id, dir, ops);
router.navigate({ await router.navigate({
to: '/workspaces/$workspaceId', to: '/workspaces/$workspaceId',
params: { workspaceId: workspace.id }, params: { workspaceId: workspace.id },
}); });

View File

@@ -1,6 +1,7 @@
import type { DnsOverride, Workspace } from '@yaakapp-internal/models'; import type { DnsOverride, Workspace } from '@yaakapp-internal/models';
import { patchModel } from '@yaakapp-internal/models'; import { patchModel } from '@yaakapp-internal/models';
import { useCallback, useId, useMemo } from 'react'; import { useCallback, useId, useMemo } from 'react';
import { fireAndForget } from '../lib/fireAndForget';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { Checkbox } from './core/Checkbox'; import { Checkbox } from './core/Checkbox';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
@@ -29,7 +30,7 @@ export function DnsOverridesEditor({ workspace }: Props) {
const handleChange = useCallback( const handleChange = useCallback(
(overrides: DnsOverride[]) => { (overrides: DnsOverride[]) => {
patchModel(workspace, { settingDnsOverrides: overrides }); fireAndForget(patchModel(workspace, { settingDnsOverrides: overrides }));
}, },
[workspace], [workspace],
); );

View File

@@ -9,6 +9,7 @@ import {
useEnvironmentsBreakdown, useEnvironmentsBreakdown,
} from '../hooks/useEnvironmentsBreakdown'; } from '../hooks/useEnvironmentsBreakdown';
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm'; import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
import { fireAndForget } from '../lib/fireAndForget';
import { jotaiStore } from '../lib/jotai'; import { jotaiStore } from '../lib/jotai';
import { isBaseEnvironment, isSubEnvironment } from '../lib/model_util'; import { isBaseEnvironment, isSubEnvironment } from '../lib/model_util';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
@@ -199,7 +200,7 @@ function EnvironmentEditDialogSidebar({
// Not sure why this is needed, but without it the // Not sure why this is needed, but without it the
// edit input blurs immediately after opening. // edit input blurs immediately after opening.
requestAnimationFrame(() => { requestAnimationFrame(() => {
actions['sidebar.selected.rename'].cb(items); fireAndForget(actions['sidebar.selected.rename'].cb(items));
}); });
}, },
}, },

View File

@@ -8,6 +8,7 @@ import { allRequestsAtom } from '../hooks/useAllRequests';
import { useFolderActions } from '../hooks/useFolderActions'; import { useFolderActions } from '../hooks/useFolderActions';
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse'; import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
import { sendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; import { sendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
import { fireAndForget } from '../lib/fireAndForget';
import { showDialog } from '../lib/dialog'; import { showDialog } from '../lib/dialog';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
import { router } from '../lib/router'; import { router } from '../lib/router';
@@ -45,7 +46,7 @@ export function FolderLayout({ folder, style }: Props) {
}, [folder.id, folders, requests]); }, [folder.id, folders, requests]);
const handleSendAll = useCallback(() => { const handleSendAll = useCallback(() => {
sendAllAction?.call(folder); if (sendAllAction) fireAndForget(sendAllAction.call(folder));
}, [sendAllAction, folder]); }, [sendAllAction, folder]);
return ( return (

View File

@@ -1,6 +1,7 @@
import { clear, readText } from '@tauri-apps/plugin-clipboard-manager'; import { clear, readText } from '@tauri-apps/plugin-clipboard-manager';
import * as m from 'motion/react-m'; import * as m from 'motion/react-m';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { fireAndForget } from '../lib/fireAndForget';
import { useImportCurl } from '../hooks/useImportCurl'; import { useImportCurl } from '../hooks/useImportCurl';
import { useWindowFocus } from '../hooks/useWindowFocus'; import { useWindowFocus } from '../hooks/useWindowFocus';
import { Button } from './core/Button'; import { Button } from './core/Button';
@@ -15,7 +16,7 @@ export function ImportCurlButton() {
// biome-ignore lint/correctness/useExhaustiveDependencies: none // biome-ignore lint/correctness/useExhaustiveDependencies: none
useEffect(() => { useEffect(() => {
readText().then(setClipboardText); fireAndForget(readText().then(setClipboardText));
}, [focused]); }, [focused]);
if (!clipboardText?.trim().startsWith('curl ')) { if (!clipboardText?.trim().startsWith('curl ')) {

View File

@@ -2,6 +2,7 @@ import { linter } from '@codemirror/lint';
import type { HttpRequest } from '@yaakapp-internal/models'; import type { HttpRequest } from '@yaakapp-internal/models';
import { patchModel } from '@yaakapp-internal/models'; import { patchModel } from '@yaakapp-internal/models';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { fireAndForget } from '../lib/fireAndForget';
import { useKeyValue } from '../hooks/useKeyValue'; import { useKeyValue } from '../hooks/useKeyValue';
import { textLikelyContainsJsonComments } from '../lib/jsonComments'; import { textLikelyContainsJsonComments } from '../lib/jsonComments';
import { Banner } from './core/Banner'; import { Banner } from './core/Banner';
@@ -58,12 +59,12 @@ export function JsonBodyEditor({ forceUpdateKey, heightMode, request }: Props) {
} else { } else {
delete newBody.sendJsonComments; delete newBody.sendJsonComments;
} }
patchModel(request, { body: newBody }); fireAndForget(patchModel(request, { body: newBody }));
}, [request, autoFix]); }, [request, autoFix]);
const handleDropdownOpen = useCallback(() => { const handleDropdownOpen = useCallback(() => {
if (!bannerDismissed) { if (!bannerDismissed) {
setBannerDismissed(true); fireAndForget(setBannerDismissed(true));
} }
}, [bannerDismissed, setBannerDismissed]); }, [bannerDismissed, setBannerDismissed]);

View File

@@ -5,6 +5,7 @@ import { getRecentCookieJars } from '../hooks/useRecentCookieJars';
import { getRecentEnvironments } from '../hooks/useRecentEnvironments'; import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
import { getRecentRequests } from '../hooks/useRecentRequests'; import { getRecentRequests } from '../hooks/useRecentRequests';
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces'; import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
import { fireAndForget } from '../lib/fireAndForget';
import { router } from '../lib/router'; import { router } from '../lib/router';
export function RedirectToLatestWorkspace() { export function RedirectToLatestWorkspace() {
@@ -20,7 +21,7 @@ export function RedirectToLatestWorkspace() {
return; return;
} }
(async () => { fireAndForget((async () => {
const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? 'n/a'; const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? 'n/a';
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null; const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null;
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null; const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null;
@@ -34,7 +35,7 @@ export function RedirectToLatestWorkspace() {
console.log('Redirecting to workspace', params, search); console.log('Redirecting to workspace', params, search);
await router.navigate({ to: '/workspaces/$workspaceId', params, search }); await router.navigate({ to: '/workspaces/$workspaceId', params, search });
})(); })());
}, [recentWorkspaces, workspaces, workspaces.length]); }, [recentWorkspaces, workspaces, workspaces.length]);
return null; return null;

View File

@@ -43,6 +43,7 @@ import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { getWebsocketRequestActions } from '../hooks/useWebsocketRequestActions'; import { getWebsocketRequestActions } from '../hooks/useWebsocketRequestActions';
import { deepEqualAtom } from '../lib/atoms'; import { deepEqualAtom } from '../lib/atoms';
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm'; import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
import { fireAndForget } from '../lib/fireAndForget';
import { jotaiStore } from '../lib/jotai'; import { jotaiStore } from '../lib/jotai';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
import { isSidebarFocused } from '../lib/scopes'; import { isSidebarFocused } from '../lib/scopes';
@@ -439,7 +440,7 @@ function Sidebar({ className }: { className?: string }) {
leftSlot: <Icon icon="arrow_right_circle" />, leftSlot: <Icon icon="arrow_right_circle" />,
hidden: workspaces.length <= 1 || requestItems.length === 0 || requestItems.length !== items.length, hidden: workspaces.length <= 1 || requestItems.length === 0 || requestItems.length !== items.length,
onSelect: () => { onSelect: () => {
actions['sidebar.selected.move'].cb(items); fireAndForget(actions['sidebar.selected.move'].cb(items));
}, },
}, },
{ {

View File

@@ -127,7 +127,7 @@ export function WorkspaceEncryptionSetting({ size, expanded, onDone, onEnabledEn
await enableEncryption(workspaceMeta.workspaceId); await enableEncryption(workspaceMeta.workspaceId);
setJustEnabledEncryption(true); setJustEnabledEncryption(true);
} catch (err) { } catch (err) {
setError(`Failed to enable encryption: ${err}`); setError(`Failed to enable encryption: ${err instanceof Error ? err.message : String(err)}`);
} }
}} }}
> >

View File

@@ -25,6 +25,7 @@ import {
} from 'react'; } from 'react';
import { useKey, useWindowSize } from 'react-use'; import { useKey, useWindowSize } from 'react-use';
import { useClickOutside } from '../../hooks/useClickOutside'; import { useClickOutside } from '../../hooks/useClickOutside';
import { fireAndForget } from '../../lib/fireAndForget';
import type { HotkeyAction } from '../../hooks/useHotKey'; import type { HotkeyAction } from '../../hooks/useHotKey';
import { useHotKey } from '../../hooks/useHotKey'; import { useHotKey } from '../../hooks/useHotKey';
import { useStateWithDeps } from '../../hooks/useStateWithDeps'; import { useStateWithDeps } from '../../hooks/useStateWithDeps';
@@ -614,7 +615,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
setActiveSubmenu({ item, parent, viaKeyboard: true }); setActiveSubmenu({ item, parent, viaKeyboard: true });
} }
} else if (item.onSelect) { } else if (item.onSelect) {
handleSelect(item); fireAndForget(handleSelect(item));
} }
}, },
{}, {},

View File

@@ -22,6 +22,7 @@ import {
useState, useState,
} from 'react'; } from 'react';
import { useKeyValue } from '../../../hooks/useKeyValue'; import { useKeyValue } from '../../../hooks/useKeyValue';
import { fireAndForget } from '../../../lib/fireAndForget';
import { computeSideForDragMove } from '../../../lib/dnd'; import { computeSideForDragMove } from '../../../lib/dnd';
import { DropMarker } from '../../DropMarker'; import { DropMarker } from '../../DropMarker';
import { ErrorBoundary } from '../../ErrorBoundary'; import { ErrorBoundary } from '../../ErrorBoundary';
@@ -143,7 +144,7 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
forwardedRef, forwardedRef,
() => ({ () => ({
setActiveTab: (value: string) => { setActiveTab: (value: string) => {
onChangeValue(value); fireAndForget(onChangeValue(value));
}, },
}), }),
[onChangeValue], [onChangeValue],

View File

@@ -11,6 +11,7 @@ import { useRandomKey } from '../../hooks/useRandomKey';
import { sync } from '../../init/sync'; import { sync } from '../../init/sync';
import { showConfirm, showConfirmDelete } from '../../lib/confirm'; import { showConfirm, showConfirmDelete } from '../../lib/confirm';
import { showDialog } from '../../lib/dialog'; import { showDialog } from '../../lib/dialog';
import { fireAndForget } from '../../lib/fireAndForget';
import { showPrompt } from '../../lib/prompt'; import { showPrompt } from '../../lib/prompt';
import { showErrorToast, showToast } from '../../lib/toast'; import { showErrorToast, showToast } from '../../lib/toast';
import { Banner } from '../core/Banner'; import { Banner } from '../core/Banner';
@@ -246,7 +247,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
message: 'Changes have been reset', message: 'Changes have been reset',
color: 'success', color: 'success',
}); });
sync({ force: true }); fireAndForget(sync({ force: true }));
}, },
onError(err) { onError(err) {
showErrorToast({ showErrorToast({
@@ -293,7 +294,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
</> </>
), ),
}); });
sync({ force: true }); fireAndForget(sync({ force: true }));
}, },
onError(err) { onError(err) {
showErrorToast({ showErrorToast({

View File

@@ -270,7 +270,7 @@ function GqlTypeInfo({
{Object.entries(fields).map(([fieldName, field]) => { {Object.entries(fields).map(([fieldName, field]) => {
const fieldItem: ExplorerItem = toExplorerItem(field, item); const fieldItem: ExplorerItem = toExplorerItem(field, item);
return ( return (
<div key={`${field.type}::${field.name}`} className="my-4"> <div key={`${String(field.type)}::${field.name}`} className="my-4">
<GqlTypeRow <GqlTypeRow
item={fieldItem} item={fieldItem}
setItem={setItem} setItem={setItem}
@@ -363,7 +363,7 @@ function GqlTypeInfo({
<Subheading>Arguments</Subheading> <Subheading>Arguments</Subheading>
{item.type.args.map((a) => { {item.type.args.map((a) => {
return ( return (
<div key={`${a.type}::${a.name}`} className="my-4"> <div key={`${String(a.type)}::${a.name}`} className="my-4">
<GqlTypeRow <GqlTypeRow
name={{ value: a.name, color: 'info' }} name={{ value: a.name, color: 'info' }}
item={{ kind: 'type', type: a.type, from: item }} item={{ kind: 'type', type: a.type, from: item }}
@@ -393,7 +393,7 @@ function GqlTypeInfo({
from: item, from: item,
}; };
return ( return (
<div key={`${field.type}::${field.name}`} className="my-4"> <div key={`${String(field.type)}::${field.name}`} className="my-4">
<GqlTypeRow <GqlTypeRow
item={fieldItem} item={fieldItem}
setItem={setItem} setItem={setItem}
@@ -431,7 +431,7 @@ function GqlTypeInfo({
if (field == null) return null; if (field == null) return null;
const fieldItem: ExplorerItem = { kind: 'field', type: field, from: item }; const fieldItem: ExplorerItem = { kind: 'field', type: field, from: item };
return ( return (
<div key={`${field.type}::${field.name}`} className="my-4"> <div key={`${String(field.type)}::${field.name}`} className="my-4">
<GqlTypeRow <GqlTypeRow
item={fieldItem} item={fieldItem}
setItem={setItem} setItem={setItem}
@@ -512,7 +512,7 @@ function GqlTypeRow({
<span className="text-text-subtle">(</span> <span className="text-text-subtle">(</span>
{item.type.args.map((arg) => ( {item.type.args.map((arg) => (
<div <div
key={`${arg.type}::${arg.name}`} key={`${String(arg.type)}::${arg.name}`}
className={classNames(item.type.args.length === 1 && 'inline-flex')} className={classNames(item.type.args.length === 1 && 'inline-flex')}
> >
{item.type.args.length > 1 && <>&nbsp;&nbsp;</>} {item.type.args.length > 1 && <>&nbsp;&nbsp;</>}

View File

@@ -6,13 +6,14 @@ import type { PDFDocumentProxy } from 'pdfjs-dist';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Document, Page } from 'react-pdf'; import { Document, Page } from 'react-pdf';
import { useContainerSize } from '../../hooks/useContainerQuery'; 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.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs', 'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url, import.meta.url,
).toString(); ).toString();
}); }));
interface Props { interface Props {
bodyPath?: string; bodyPath?: string;

View File

@@ -1,6 +1,7 @@
// Listen for settings changes, the re-compute theme // Listen for settings changes, the re-compute theme
import { listen } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event';
import type { ModelPayload } from '@yaakapp-internal/models'; import type { ModelPayload } from '@yaakapp-internal/models';
import { fireAndForget } from './lib/fireAndForget';
import { getSettings } from './lib/settings'; import { getSettings } from './lib/settings';
function setFontSizeOnDocument(fontSize: number) { function setFontSizeOnDocument(fontSize: number) {
@@ -13,4 +14,4 @@ listen<ModelPayload>('model_write', async (event) => {
setFontSizeOnDocument(event.payload.model.interfaceFontSize); setFontSizeOnDocument(event.payload.model.interfaceFontSize);
}).catch(console.error); }).catch(console.error);
getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize)); fireAndForget(getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize)));

View File

@@ -1,6 +1,7 @@
// Listen for settings changes, the re-compute theme // Listen for settings changes, the re-compute theme
import { listen } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event';
import type { ModelPayload, Settings } from '@yaakapp-internal/models'; import type { ModelPayload, Settings } from '@yaakapp-internal/models';
import { fireAndForget } from './lib/fireAndForget';
import { getSettings } from './lib/settings'; import { getSettings } from './lib/settings';
function setFonts(settings: Settings) { function setFonts(settings: Settings) {
@@ -17,4 +18,4 @@ listen<ModelPayload>('model_write', async (event) => {
setFonts(event.payload.model); setFonts(event.payload.model);
}).catch(console.error); }).catch(console.error);
getSettings().then((settings) => setFonts(settings)); fireAndForget(getSettings().then((settings) => setFonts(settings)));

View File

@@ -16,7 +16,7 @@ interface TypeMap {
} }
export function useActiveRequest<T extends keyof TypeMap>( export function useActiveRequest<T extends keyof TypeMap>(
model?: T | undefined, model?: T,
): TypeMap[T] | null { ): TypeMap[T] | null {
const activeRequest = useAtomValue(activeRequestAtom); const activeRequest = useAtomValue(activeRequestAtom);
if (model == null) return activeRequest as TypeMap[T]; if (model == null) return activeRequest as TypeMap[T];

View File

@@ -18,7 +18,7 @@ export function useCreateDropdownItems({
}: { }: {
hideFolder?: boolean; hideFolder?: boolean;
hideIcons?: boolean; hideIcons?: boolean;
folderId?: string | null | 'active-folder'; folderId?: string | null;
} = {}): DropdownItem[] { } = {}): DropdownItem[] {
const workspaceId = useAtomValue(activeWorkspaceIdAtom); const workspaceId = useAtomValue(activeWorkspaceIdAtom);
const activeRequest = useAtomValue(activeRequestAtom); const activeRequest = useAtomValue(activeRequestAtom);
@@ -40,7 +40,7 @@ export function getCreateDropdownItems({
}: { }: {
hideFolder?: boolean; hideFolder?: boolean;
hideIcons?: boolean; hideIcons?: boolean;
folderId?: string | null | 'active-folder'; folderId?: string | null;
workspaceId: string | null; workspaceId: string | null;
activeRequest: HttpRequest | GrpcRequest | WebsocketRequest | null; activeRequest: HttpRequest | GrpcRequest | WebsocketRequest | null;
onCreate?: ( onCreate?: (

View File

@@ -42,7 +42,7 @@ export function createFastMutation<TData = unknown, TError = unknown, TVariables
if (!disableToastError) { if (!disableToastError) {
showToast({ showToast({
id: stringKey, id: stringKey,
message: `${err}`, message: err instanceof Error ? err.message : String(err),
color: 'danger', color: 'danger',
timeout: 5000, timeout: 5000,
}); });

View File

@@ -7,6 +7,7 @@ import {
} from '@yaakapp-internal/models'; } from '@yaakapp-internal/models';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { fireAndForget } from '../lib/fireAndForget';
export function useHttpResponseEvents(response: HttpResponse | null) { export function useHttpResponseEvents(response: HttpResponse | null) {
const allEvents = useAtomValue(httpResponseEventsAtom); const allEvents = useAtomValue(httpResponseEventsAtom);
@@ -18,10 +19,10 @@ export function useHttpResponseEvents(response: HttpResponse | null) {
} }
// Fetch events from database, filtering out events from other responses and merging atomically // Fetch events from database, filtering out events from other responses and merging atomically
invoke<HttpResponseEvent[]>('cmd_get_http_response_events', { responseId: response.id }).then( fireAndForget(invoke<HttpResponseEvent[]>('cmd_get_http_response_events', { responseId: response.id }).then(
(events) => (events) =>
mergeModelsInStore('http_response_event', events, (e) => e.responseId === response.id), mergeModelsInStore('http_response_event', events, (e) => e.responseId === response.id),
); ));
}, [response?.id]); }, [response?.id]);
const events = allEvents.filter((e) => e.responseId === response?.id); const events = allEvents.filter((e) => e.responseId === response?.id);

View File

@@ -8,6 +8,7 @@ import {
} from '@yaakapp-internal/models'; } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai'; import { atom, useAtomValue } from 'jotai';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { fireAndForget } from '../lib/fireAndForget';
import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage'; import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage';
import { activeRequestIdAtom } from './useActiveRequestId'; 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 // Fetch events from database, filtering out events from other connections and merging atomically
invoke<GrpcEvent[]>('models_grpc_events', { connectionId }).then((events) => fireAndForget(invoke<GrpcEvent[]>('models_grpc_events', { connectionId }).then((events) =>
mergeModelsInStore('grpc_event', events, (e) => e.connectionId === connectionId), mergeModelsInStore('grpc_event', events, (e) => e.connectionId === connectionId),
); ));
}, [connectionId]); }, [connectionId]);
return useMemo( return useMemo(

View File

@@ -8,6 +8,7 @@ import {
} from '@yaakapp-internal/models'; } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai'; import { atom, useAtomValue } from 'jotai';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { fireAndForget } from '../lib/fireAndForget';
import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage'; import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage';
import { jotaiStore } from '../lib/jotai'; import { jotaiStore } from '../lib/jotai';
import { activeRequestIdAtom } from './useActiveRequestId'; 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 // Fetch events from database, filtering out events from other connections and merging atomically
invoke<WebsocketEvent[]>('models_websocket_events', { connectionId }).then((events) => fireAndForget(invoke<WebsocketEvent[]>('models_websocket_events', { connectionId }).then((events) =>
mergeModelsInStore('websocket_event', events, (e) => e.connectionId === connectionId), mergeModelsInStore('websocket_event', events, (e) => e.connectionId === connectionId),
); ));
}, [connectionId]); }, [connectionId]);
return useMemo( return useMemo(

View File

@@ -1,5 +1,6 @@
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { fireAndForget } from '../lib/fireAndForget';
export function useWindowFocus() { export function useWindowFocus() {
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
@@ -10,7 +11,7 @@ export function useWindowFocus() {
}); });
return () => { return () => {
unlisten.then((fn) => fn()); fireAndForget(unlisten.then((fn) => fn()));
}; };
}, []); }, []);

View File

@@ -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<unknown>) {
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),
});
});
}

View File

@@ -22,6 +22,7 @@ import { HStack, VStack } from '../components/core/Stacks';
// Listen for toasts // Listen for toasts
import { listenToTauriEvent } from '../hooks/useListenToTauriEvent'; import { listenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { fireAndForget } from './fireAndForget';
import { updateAvailableAtom } from './atoms'; import { updateAvailableAtom } from './atoms';
import { stringToColor } from './color'; import { stringToColor } from './color';
import { generateId } from './generateId'; import { generateId } from './generateId';
@@ -81,7 +82,7 @@ export function initGlobalListeners() {
done, done,
}, },
}; };
emit(event.id, result); fireAndForget(emit(event.id, result));
}; };
const values = await showPromptForm({ const values = await showPromptForm({
@@ -110,7 +111,7 @@ export function initGlobalListeners() {
// Listen for update events // Listen for update events
listenToTauriEvent<UpdateInfo>('update_available', async ({ payload }) => { listenToTauriEvent<UpdateInfo>('update_available', async ({ payload }) => {
console.log('Got update available', payload); console.log('Got update available', payload);
showUpdateAvailableToast(payload); fireAndForget(showUpdateAvailableToast(payload));
}); });
listenToTauriEvent<YaakNotification>('notification', ({ payload }) => { listenToTauriEvent<YaakNotification>('notification', ({ payload }) => {
@@ -125,7 +126,7 @@ export function initGlobalListeners() {
}); });
// Check for plugin initialization errors // 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) { for (const [dir, message] of errors) {
const dirBasename = dir.split('/').pop() ?? dir; const dirBasename = dir.split('/').pop() ?? dir;
showToast({ showToast({
@@ -155,7 +156,7 @@ export function initGlobalListeners() {
), ),
}); });
} }
}); }));
} }
function showUpdateInstalledToast(version: string) { function showUpdateInstalledToast(version: string) {

View File

@@ -1,4 +1,5 @@
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import { fireAndForget } from '../fireAndForget';
export type Appearance = 'light' | 'dark'; export type Appearance = 'light' | 'dark';
@@ -22,13 +23,13 @@ export function subscribeToWindowAppearanceChange(
unsubscribe: () => {}, unsubscribe: () => {},
}; };
getCurrentWebviewWindow() fireAndForget(getCurrentWebviewWindow()
.onThemeChanged((t) => { .onThemeChanged((t) => {
cb(t.payload); cb(t.payload);
}) })
.then((l) => { .then((l) => {
container.unsubscribe = l; container.unsubscribe = l;
}); }));
return () => container.unsubscribe(); return () => container.unsubscribe();
} }
@@ -43,6 +44,6 @@ export function resolveAppearance(
export function subscribeToPreferredAppearance(cb: (a: Appearance) => void) { export function subscribeToPreferredAppearance(cb: (a: Appearance) => void) {
cb(getCSSAppearance()); cb(getCSSAppearance());
getWindowAppearance().then(cb); fireAndForget(getWindowAppearance().then(cb));
subscribeToWindowAppearanceChange(cb); subscribeToWindowAppearanceChange(cb);
} }

View File

@@ -138,6 +138,7 @@ module.exports = {
}, },
plugins: [ plugins: [
require('@tailwindcss/container-queries'), require('@tailwindcss/container-queries'),
// oxlint-disable-next-line unbound-method
plugin(function ({ addVariant }) { plugin(function ({ addVariant }) {
addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']); addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']);
addVariant('focus-visible-or-class', ['&:focus-visible', '&.focus:focus']); addVariant('focus-visible-or-class', ['&:focus-visible', '&.focus:focus']);