Migrate to Vite+ unified toolchain (#428)

This commit is contained in:
Gregory Schier
2026-03-13 09:27:56 -07:00
committed by GitHub
parent aed7bd12ea
commit 45262edfbd
166 changed files with 1762 additions and 1519 deletions

View File

@@ -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],
);

View File

@@ -512,16 +512,14 @@ function HttpRequestArg({
help={arg.description}
value={value}
disabled={arg.disabled}
options={[
...httpRequests.map((r) => {
options={httpRequests.map((r) => {
return {
label:
buildRequestBreadcrumbs(r, folders).join(' / ') +
(r.id === activeHttpRequest?.id ? ' (current)' : ''),
value: r.id,
};
}),
]}
})}
/>
);
}

View File

@@ -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';
@@ -112,7 +113,7 @@ function EnvironmentEditDialogSidebar({
const treeRef = useRef<TreeHandle>(null);
const { baseEnvironment, baseEnvironments } = useEnvironmentsBreakdown();
// biome-ignore lint/correctness/useExhaustiveDependencies: none
// oxlint-disable-next-line react-hooks/exhaustive-deps
useLayoutEffect(() => {
if (selectedEnvironmentId == null) return;
treeRef.current?.selectItem(selectedEnvironmentId);
@@ -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));
});
},
},

View File

@@ -55,7 +55,7 @@ function ExportDataDialogContent({
const handleToggleAll = () => {
setSelectedWorkspaces(
// biome-ignore lint/performance/noAccumulatingSpread: none
// oxlint-disable-next-line no-accumulating-spread
allSelected ? {} : workspaces.reduce((acc, w) => ({ ...acc, [w.id]: true }), {}),
);
};

View File

@@ -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 (

View File

@@ -103,7 +103,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
Found services{' '}
{services?.slice(0, 5).map((s, i) => {
return (
<span key={s.name + s.methods.join(',')}>
<span key={s.name + s.methods.map((m) => m.name).join(',')}>
<InlineCode>{s.name}</InlineCode>
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
</span>
@@ -119,7 +119,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
Server reflection found services
{services?.map((s, i) => {
return (
<span key={s.name + s.methods.join(',')}>
<span key={s.name + s.methods.map((m) => m.name).join(',')}>
<InlineCode>{s.name}</InlineCode>
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
</span>
@@ -144,7 +144,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
{protoFiles.map((f, i) => {
const parts = f.split('/');
return (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<tr key={f + i} className="group">
<td>
<Icon icon={f.endsWith('.proto') ? 'file_code' : 'folder_code'} />

View File

@@ -50,7 +50,7 @@ export function GrpcResponsePane({ style, methodType, activeRequest }: Props) {
);
// Set the active message to the first message received if unary
// biome-ignore lint/correctness/useExhaustiveDependencies: none
// oxlint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (events.length === 0 || activeEvent != null || methodType !== 'unary') {
return;

View File

@@ -13,9 +13,9 @@ export function ImportCurlButton() {
const importCurl = useImportCurl();
const [isLoading, setIsLoading] = useState(false);
// biome-ignore lint/correctness/useExhaustiveDependencies: none
// oxlint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
readText().then(setClipboardText);
readText().then(setClipboardText).catch(() => {});
}, [focused]);
if (!clipboardText?.trim().startsWith('curl ')) {

View File

@@ -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]);

View File

@@ -102,7 +102,7 @@ const markdownComponents: Partial<Components> = {
language={match[1]}
style={prismTheme}
>
{String(children).replace(/\n$/, '')}
{String(children as string).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code {...extraProps} ref={ref} className={className}>

View File

@@ -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;

View File

@@ -130,7 +130,7 @@ export function ResponseCookies({ response }: Props) {
) : (
<KeyValueRows>
{sentCookies.map((cookie, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<KeyValueRow labelColor="primary" key={i} label={cookie.name}>
{cookie.value}
</KeyValueRow>
@@ -153,7 +153,7 @@ export function ResponseCookies({ response }: Props) {
) : (
<div className="flex flex-col gap-4">
{receivedCookies.map((cookie, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<div key={i} className="flex flex-col gap-1">
<div className="flex items-center gap-2 my-1">
<span

View File

@@ -62,7 +62,7 @@ export function ResponseHeaders({ response }: Props) {
) : (
<KeyValueRows>
{requestHeaders.map((h, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<KeyValueRow labelColor="primary" key={i} label={h.name}>
{h.value}
</KeyValueRow>
@@ -84,7 +84,7 @@ export function ResponseHeaders({ response }: Props) {
) : (
<KeyValueRows>
{responseHeaders.map((h, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<KeyValueRow labelColor="info" key={i} label={h.name}>
{h.value}
</KeyValueRow>

View File

@@ -7,7 +7,7 @@ import { VStack } from './core/Stacks';
export default function RouteError({ error }: { error: unknown }) {
console.log('Error', error);
const stringified = JSON.stringify(error);
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
const message = (error as any).message ?? stringified;
const stack =
typeof error === 'object' && error != null && 'stack' in error ? String(error.stack) : null;

View File

@@ -238,7 +238,7 @@ export function SettingsCertificates() {
<VStack space={3}>
{certificates.map((cert, index) => (
<CertificateEditor
// biome-ignore lint/suspicious/noArrayIndexKey: Index is fine here
// oxlint-disable-next-line react/no-array-index-key
key={index}
certificate={cert}
index={index}

View File

@@ -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: <Icon icon="arrow_right_circle" />,
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));
},
},
{

View File

@@ -141,7 +141,7 @@ function InitializedTemplateFunctionDialog({
});
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
// biome-ignore lint/correctness/useExhaustiveDependencies: Only update this on rendered data change to keep secrets hidden on input change
// oxlint-disable-next-line react-hooks/exhaustive-deps -- Only update this on rendered data change to keep secrets hidden on input change
const dataContainsSecrets = useMemo(() => {
for (const [name, value] of Object.entries(argValues)) {
const arg = templateFunction.data?.args.find((a) => 'name' in a && a.name === name);

View File

@@ -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)}`);
}
}}
>
@@ -285,7 +285,7 @@ function HighlightedKey({ keyText, show }: { keyText: string; show: boolean }) {
keyText.split('').map((c, i) => {
return (
<span
// biome-ignore lint/suspicious/noArrayIndexKey: it's fine
// oxlint-disable-next-line react/no-array-index-key
key={i}
className={classNames(
c.match(/[0-9]/) && 'text-info',

View File

@@ -54,7 +54,7 @@ export function AutoScroller<T>({
useLayoutEffect(() => {
if (!autoScroll) return;
data.length; // Make linter happy. We want to refresh when length changes
void data.length; // Trigger refresh when length changes
const el = containerRef.current;
if (el == null) return;

View File

@@ -22,7 +22,7 @@ export function DetailsBanner({
storageKey,
...extraProps
}: Props) {
// biome-ignore lint/correctness/useExhaustiveDependencies: We only want to recompute the atom when storageKey changes
// oxlint-disable-next-line react-hooks/exhaustive-deps -- We only want to recompute the atom when storageKey changes
const openAtom = useMemo(
() =>
storageKey

View File

@@ -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<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
setActiveSubmenu({ item, parent, viaKeyboard: true });
}
} else if (item.onSelect) {
handleSelect(item);
fireAndForget(handleSelect(item));
}
},
{},
@@ -752,7 +753,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
if (item.type === 'separator') {
return (
<Separator
// biome-ignore lint/suspicious/noArrayIndexKey: Nothing else available
// oxlint-disable-next-line react/no-array-index-key -- Nothing else available
key={i}
className={classNames('my-1.5', item.label ? 'ml-2' : null)}
>
@@ -762,8 +763,8 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
}
if (item.type === 'content') {
return (
// biome-ignore lint/a11y/noStaticElementInteractions: Needs to be clickable but want to support nested buttons
// biome-ignore lint/suspicious/noArrayIndexKey: index is fine
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions
// oxlint-disable-next-line react/no-array-index-key
<div key={i} className={classNames('my-1 mx-2 max-w-xs')} onClick={onClose}>
{item.label}
</div>
@@ -777,7 +778,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
onFocus={handleFocus}
onSelect={handleSelect}
onHover={handleItemHover}
// biome-ignore lint/suspicious/noArrayIndexKey: It's fine
// oxlint-disable-next-line react/no-array-index-key
key={i}
item={item}
/>
@@ -785,7 +786,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
})}
</VStack>
{activeSubmenu && (
// biome-ignore lint/a11y/noStaticElementInteractions: Container div that cancels hover timeout
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- Container div that cancels hover timeout
<div
ref={submenuRef}
onMouseEnter={() => {

View File

@@ -327,7 +327,7 @@ function EditorInner({
);
// Update the language extension when the language changes
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally limited deps
// oxlint-disable-next-line react-hooks/exhaustive-deps -- intentionally limited deps
useEffect(() => {
if (cm.current === null) return;
const { view, languageCompartment } = cm.current;
@@ -361,7 +361,7 @@ function EditorInner({
]);
// Initialize the editor when ref mounts
// biome-ignore lint/correctness/useExhaustiveDependencies: only reinitialize when necessary
// oxlint-disable-next-line react-hooks/exhaustive-deps -- only reinitialize when necessary
const initEditorRef = useCallback(
function initEditorRef(container: HTMLDivElement | null) {
if (container === null) {

View File

@@ -1,4 +1,4 @@
// biome-ignore-all lint: Disable for generated file
/* oxlint-disable */
// This file was generated by lezer-generator. You probably shouldn't edit it.
import { LRParser } from '@lezer/lr';
import { highlight } from './highlight';

View File

@@ -14,7 +14,7 @@ const tooltip = hoverTooltip(
let match: RegExpExecArray | null;
let found: { start: number; end: number } | null = null;
// biome-ignore lint/suspicious/noAssignInExpressions: none
// oxlint-disable-next-line no-cond-assign
while ((match = REGEX.exec(text))) {
const start = from + match.index;
const end = start + match[0].length;

View File

@@ -20,7 +20,7 @@ export function jsonParseLinter(options?: JsonLintOptions) {
mode: (options?.allowComments ?? true) ? 'cjson' : 'json',
ignoreTrailingCommas: options?.allowTrailingCommas ?? false,
});
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
} catch (err: any) {
if (!('location' in err)) {
return [];

View File

@@ -1,6 +1,6 @@
// biome-ignore-all lint/suspicious/noTemplateCurlyInString: We're testing this, specifically
/* oxlint-disable no-template-curly-in-string */
import { describe, expect, test } from 'vitest';
import { describe, expect, test } from 'vite-plus/test';
import { parser } from './twig';
function getNodeNames(input: string): string[] {

View File

@@ -35,7 +35,7 @@ export function HotkeyRaw({ labelParts, className, variant }: HotkeyRawProps) {
)}
>
{labelParts.map((char, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<div key={index} className="min-w-[1em] text-center">
{char}
</div>

View File

@@ -144,7 +144,7 @@ function BaseInput({
isFocused: () => editorRef.current?.hasFocus ?? false,
value: () => editorRef.current?.state.doc.toString() ?? '',
dispatch: (...args) => {
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
editorRef.current?.dispatch(...(args as any));
},
selectAll() {
@@ -329,7 +329,7 @@ function BaseInput({
</HStack>
{type === 'password' && !disableObscureToggle && (
<IconButton
title={obscured ? `Show ${label}` : `Obscure ${label}`}
title={obscured ? `Show ${typeof label === 'string' ? label : 'field'}` : `Obscure ${typeof label === 'string' ? label : 'field'}`}
size="xs"
className={classNames('mr-0.5 !h-auto my-0.5', disabled && 'opacity-disabled')}
color={tint}

View File

@@ -5,7 +5,7 @@ import { Icon } from './Icon';
interface Props {
depth?: number;
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
attrValue: any;
attrKey?: string | number;
attrKeyJsonPath?: string;
@@ -54,10 +54,10 @@ export const JsonAttributeTree = ({
if (jsonType === '[object Array]') {
return {
children: isExpanded
? // biome-ignore lint/suspicious/noExplicitAny: none
? // oxlint-disable-next-line no-explicit-any
attrValue.flatMap((v: any, i: number) => (
<JsonAttributeTree
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
key={i}
depth={depth + 1}
attrValue={v}

View File

@@ -13,7 +13,7 @@ export function KeyValueRows({ children }: Props) {
<table className="text-editor font-mono min-w-0 w-full mb-auto">
<tbody className="divide-y divide-surface-highlight">
{childArray.map((child, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<tr key={i}>{child}</tr>
))}
</tbody>

View File

@@ -37,7 +37,7 @@ export function Label({
{required === true && <span className="text-text-subtlest">*</span>}
</span>
{tags.map((tag, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<span key={i} className="text-xs text-text-subtlest">
({tag})
</span>

View File

@@ -145,7 +145,7 @@ export function PairEditor({
[handle, pairs, setRef],
);
// biome-ignore lint/correctness/useExhaustiveDependencies: Only care about forceUpdateKey
// oxlint-disable-next-line react-hooks/exhaustive-deps -- Only care about forceUpdateKey
useEffect(() => {
// Remove empty headers on initial render and ensure they all have valid ids (pairs didn't use to have IDs)
const newPairs: PairWithId[] = [];

View File

@@ -195,7 +195,7 @@ export const PlainInput = forwardRef<{ focus: () => void }, PlainInputProps>(fun
key={forceUpdateKey}
type={type === 'password' && !obscured ? 'text' : type}
name={name}
// biome-ignore lint/a11y/noAutofocus: Who cares
// oxlint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
defaultValue={defaultValue ?? undefined}
autoComplete="off"
@@ -213,7 +213,7 @@ export const PlainInput = forwardRef<{ focus: () => void }, PlainInputProps>(fun
</HStack>
{type === 'password' && !hideObscureToggle && (
<IconButton
title={obscured ? `Show ${label}` : `Obscure ${label}`}
title={obscured ? `Show ${typeof label === 'string' ? label : 'field'}` : `Obscure ${typeof label === 'string' ? label : 'field'}`}
size="xs"
className="mr-0.5 group/obscure !h-auto my-0.5"
iconClassName="group-hover/obscure:text"

View File

@@ -62,13 +62,13 @@ export function SegmentedControl<T extends string>({
if (e.key === 'ArrowRight') {
e.preventDefault();
const newIndex = Math.abs((selectedIndex + 1) % options.length);
options[newIndex] && setSelectedValue(options[newIndex].value);
if (options[newIndex]) setSelectedValue(options[newIndex].value);
const child = containerRef.current?.children[newIndex] as HTMLButtonElement;
child.focus();
} else if (e.key === 'ArrowLeft') {
e.preventDefault();
const newIndex = Math.abs((selectedIndex - 1) % options.length);
options[newIndex] && setSelectedValue(options[newIndex].value);
if (options[newIndex]) setSelectedValue(options[newIndex].value);
const child = containerRef.current?.children[newIndex] as HTMLButtonElement;
child.focus();
}

View File

@@ -20,7 +20,7 @@ interface HStackProps extends BaseStackProps {
export const HStack = forwardRef(function HStack(
{ className, space, children, alignItems = 'center', ...props }: HStackProps,
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
ref: ForwardedRef<any>,
) {
return (
@@ -41,7 +41,7 @@ export type VStackProps = BaseStackProps & {
export const VStack = forwardRef(function VStack(
{ className, space, children, ...props }: VStackProps,
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
ref: ForwardedRef<any>,
) {
return (
@@ -65,7 +65,7 @@ type BaseStackProps = HTMLAttributes<HTMLElement> & {
const BaseStack = forwardRef(function BaseStack(
{ className, alignItems, justifyContent, wrap, children, as, ...props }: BaseStackProps,
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
ref: ForwardedRef<any>,
) {
const Component = as ?? 'div';

View File

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

View File

@@ -110,7 +110,7 @@ export function Tooltip({ children, className, content, tabIndex, size = 'md' }:
/>
</div>
</Portal>
{/** biome-ignore lint/a11y/useSemanticElements: Needs to be usable in other buttons */}
{/* oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- Needs to be usable in other buttons */}
<span
ref={triggerRef}
role="button"

View File

@@ -111,7 +111,7 @@ function TreeInner<T extends { id: string }>(
}, []);
// Select the first item on first render
// biome-ignore lint/correctness/useExhaustiveDependencies: Only used for initial render
// oxlint-disable-next-line react-hooks/exhaustive-deps -- Only used for initial render
useEffect(() => {
const ids = jotaiStore.get(selectedIdsFamily(treeId));
const fallback = selectableItems[0];
@@ -736,7 +736,7 @@ function DropRegionAfterList({
onContextMenu?: (e: MouseEvent<HTMLDivElement>) => void;
}) {
const { setNodeRef } = useDroppable({ id });
// biome-ignore lint/a11y/noStaticElementInteractions: Meh
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions
return <div ref={setNodeRef} onContextMenu={onContextMenu} />;
}

View File

@@ -19,7 +19,7 @@ export const TreeIndentGuide = memo(function TreeIndentGuide({
<div className="flex">
{Array.from({ length: depth }).map((_, i) => (
<div
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
key={i}
className={classNames(
'w-[calc(1rem+0.5px)] border-r border-r-text-subtlest',

View File

@@ -41,7 +41,7 @@ export function equalSubtree<T extends { id: string }>(
}
for (let i = 0; i < ak.length; i++) {
// biome-ignore lint/style/noNonNullAssertion: none
// oxlint-disable-next-line no-non-null-assertion
if (!equalSubtree(ak[i]!, bk[i]!, getItemKey)) return false;
}

View File

@@ -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({

View File

@@ -27,7 +27,7 @@ export function HistoryDialog({ log }: Props) {
</TableHead>
<TableBody>
{log.map((l) => (
<TableRow key={l.author + (l.message ?? 'n/a') + l.when}>
<TableRow key={(l.author.name ?? '') + (l.author.email ?? '') + (l.message ?? 'n/a') + l.when}>
<TruncatedWideTableCell>
{l.message || <em className="text-text-subtle">No message</em>}
</TruncatedWideTableCell>

View File

@@ -45,7 +45,7 @@ interface Props {
type ExplorerItem =
| { kind: 'type'; type: GraphQLType; from: ExplorerItem }
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
| { kind: 'field'; type: GraphQLField<any, any>; from: ExplorerItem }
| { kind: 'input_field'; type: GraphQLInputField; from: ExplorerItem }
| null;
@@ -146,7 +146,7 @@ export const GraphQLDocsExplorer = memo(function GraphQLDocsExplorer({
</div>
) : (
<div
key={activeItem.type.toString()} // Reset scroll position to top
key={'name' in activeItem.type ? activeItem.type.name : String(activeItem.type)} // Reset scroll position to top
className="overflow-y-auto h-full w-full p-3 grid grid-cols-[minmax(0,1fr)]"
>
<GqlTypeInfo item={activeItem} setItem={setActiveItem} schema={schema} />
@@ -182,14 +182,14 @@ function GraphQLExplorerHeader({
<Icon icon="book_open_text" />
{crumbs.map((crumb, i) => {
return (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<Fragment key={i}>
{i > 0 && <Icon icon="chevron_right" className="text-text-subtlest" />}
{crumb === item || item == null ? (
<GqlTypeLabel noTruncate item={item} />
) : crumb === item ? null : (
<GqlTypeLink
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
key={i}
noTruncate
item={crumb}
@@ -202,7 +202,7 @@ function GraphQLExplorerHeader({
})}
</div>
<GqlSchemaSearch
key={item?.type.toString()} // Force reset when changing items
key={item != null && 'name' in item.type ? item.type.name : 'search'} // Force reset when changing items
maxHeight={containerHeight}
currentItem={item}
schema={schema}
@@ -270,7 +270,7 @@ function GqlTypeInfo({
{Object.entries(fields).map(([fieldName, field]) => {
const fieldItem: ExplorerItem = toExplorerItem(field, item);
return (
<div key={`${field.type}::${field.name}`} className="my-4">
<div key={`${String(field.type)}::${field.name}`} className="my-4">
<GqlTypeRow
item={fieldItem}
setItem={setItem}
@@ -363,7 +363,7 @@ function GqlTypeInfo({
<Subheading>Arguments</Subheading>
{item.type.args.map((a) => {
return (
<div key={`${a.type}::${a.name}`} className="my-4">
<div key={`${String(a.type)}::${a.name}`} className="my-4">
<GqlTypeRow
name={{ value: a.name, color: 'info' }}
item={{ kind: 'type', type: a.type, from: item }}
@@ -393,7 +393,7 @@ function GqlTypeInfo({
from: item,
};
return (
<div key={`${field.type}::${field.name}`} className="my-4">
<div key={`${String(field.type)}::${field.name}`} className="my-4">
<GqlTypeRow
item={fieldItem}
setItem={setItem}
@@ -431,7 +431,7 @@ function GqlTypeInfo({
if (field == null) return null;
const fieldItem: ExplorerItem = { kind: 'field', type: field, from: item };
return (
<div key={`${field.type}::${field.name}`} className="my-4">
<div key={`${String(field.type)}::${field.name}`} className="my-4">
<GqlTypeRow
item={fieldItem}
setItem={setItem}
@@ -512,7 +512,7 @@ function GqlTypeRow({
<span className="text-text-subtle">(</span>
{item.type.args.map((arg) => (
<div
key={`${arg.type}::${arg.name}`}
key={`${String(arg.type)}::${arg.name}`}
className={classNames(item.type.args.length === 1 && 'inline-flex')}
>
{item.type.args.length > 1 && <>&nbsp;&nbsp;</>}
@@ -674,7 +674,7 @@ function Subheading({ children, count }: { children: ReactNode; count?: number }
interface SearchResult {
name: string;
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
type: GraphQLNamedType | GraphQLField<any, any> | GraphQLInputField;
score: number;
from: GraphQLNamedType | null;
@@ -798,7 +798,7 @@ function GqlSchemaSearch({
label="search"
hideLabel
defaultValue={value}
placeholder={focused ? `Search ${currentItem?.type.toString() ?? 'Schema'}` : 'Search'}
placeholder={focused ? `Search ${currentItem != null && 'name' in currentItem.type ? currentItem.type.name : 'Schema'}` : 'Search'}
leftSlot={
<div className="w-10 flex justify-center items-center">
<Icon size="sm" icon="search" color="secondary" />
@@ -897,10 +897,10 @@ function DocMarkdown({ children, className }: { children: string | null; classNa
function walkTypeGraph(
schema: GraphQLSchema,
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
start: GraphQLType | GraphQLField<any, any> | GraphQLInputField | null,
cb: (
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
type: GraphQLNamedType | GraphQLField<any, any> | GraphQLInputField,
from: GraphQLNamedType | null,
path: string[],
@@ -908,7 +908,7 @@ function walkTypeGraph(
) {
const visited = new Set<string>();
const queue: Array<{
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
current: GraphQLType | GraphQLField<any, any> | GraphQLInputField;
from: GraphQLNamedType | null;
path: string[];
@@ -928,7 +928,7 @@ function walkTypeGraph(
}
while (queue.length > 0) {
// biome-ignore lint/style/noNonNullAssertion: none
// oxlint-disable-next-line no-non-null-assertion
const { current, from, path } = queue.shift()!;
if (!isNamedType(current)) continue;
@@ -981,7 +981,7 @@ function walkTypeGraph(
}
}
// biome-ignore lint/suspicious/noExplicitAny: none
// oxlint-disable-next-line no-explicit-any
function toExplorerItem(t: any, from: ExplorerItem | null): ExplorerItem | null {
if (t == null) return null;

View File

@@ -22,6 +22,6 @@ export function AudioViewer({ bodyPath, data }: Props) {
}
}, [bodyPath, data]);
// biome-ignore lint/a11y/useMediaCaption: none
// oxlint-disable-next-line jsx-a11y/media-has-caption
return <audio className="w-full" controls src={src} />;
}

View File

@@ -36,7 +36,7 @@ export function CsvViewerInner({ text, className }: { text: string | null; class
</TableHead>
<TableBody>
{parsed.data.map((row, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: none
// oxlint-disable-next-line react/no-array-index-key
<TableRow key={i}>
{parsed.meta.fields?.map((key) => (
<TableCell key={key}>{row[key] ?? ''}</TableCell>

View File

@@ -75,7 +75,7 @@ export function MultipartViewer({ data, boundary, idPrefix = 'multipart' }: Prop
>
{parts.map((part, i) => (
<TabContent
// biome-ignore lint/suspicious/noArrayIndexKey: Nothing else to key on
// oxlint-disable-next-line react/no-array-index-key -- Nothing else to key on
key={idPrefix + part.name + i}
value={tabValue(part, i)}
className="pl-3 !pt-0"

View File

@@ -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;
@@ -56,7 +57,7 @@ export function PdfViewer({ bodyPath, data }: Props) {
externalLinkTarget="_blank"
externalLinkRel="noopener noreferrer"
>
{Array.from(new Array(numPages), (_, index) => (
{Array.from({ length: numPages ?? 0 }, (_, index) => (
<Page
className="mb-6 select-all"
renderTextLayer

View File

@@ -22,6 +22,6 @@ export function VideoViewer({ bodyPath, data }: Props) {
}
}, [bodyPath, data]);
// biome-ignore lint/a11y/useMediaCaption: none
// oxlint-disable-next-line jsx-a11y/media-has-caption
return <video className="w-full" controls src={src} />;
}