feat: log viewer changes and fixes

This commit is contained in:
LynithDev 2025-08-15 19:16:21 +02:00
parent 6d4e0fa850
commit 9db18e4d12
14 changed files with 247 additions and 56 deletions

View file

@ -16,6 +16,7 @@
"@onelauncher/common": "workspace:*",
"@tailwindcss/vite": "catalog:",
"@tanstack/match-sorter-utils": "catalog:",
"@tanstack/react-devtools": "catalog:",
"@tanstack/react-query": "catalog:",
"@tanstack/react-query-devtools": "catalog:",
"@tanstack/react-router": "catalog:",

View file

@ -228,8 +228,13 @@ export type VersionType =
*/
"old_beta"
const ARGS_MAP = { 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[]}', 'core':'{"getScreenshots":["id"],"updateClusterById":["id","request"],"getPackageBody":["provider","body"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"launchCluster":["id","uuid"],"getClusters":[],"getLoadersForVersion":["mc_version"],"getUser":["uuid"],"getGameVersions":[],"getDefaultUser":["fallback"],"getUsersFromAuthor":["provider","author"],"getProfileOrDefault":["name"],"removeCluster":["id"],"getUsers":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getMultiplePackages":["provider","slugs"],"getLogs":["id"],"getWorlds":["id"],"setDefaultUser":["uuid"],"removeUser":["uuid"],"openMsaLogin":[],"getPackage":["provider","slug"],"writeSettings":["setting"],"createCluster":["options"],"readSettings":[],"getLogByName":["id","name"],"updateClusterProfile":["name","profile"],"getClusterById":["id"],"searchPackages":["provider","query"],"getGlobalProfile":[]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' }
export type Router = { 'core': { getClusters: () => Promise<ClusterModel[]>,
const ARGS_MAP = { 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'core':'{"openMsaLogin":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getMultiplePackages":["provider","slugs"],"getGlobalProfile":[],"getLogByName":["id","name"],"getLogs":["id"],"getClusterById":["id"],"getLoadersForVersion":["mc_version"],"getDefaultUser":["fallback"],"getWorlds":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"getProfileOrDefault":["name"],"updateClusterProfile":["name","profile"],"getUser":["uuid"],"launchCluster":["id","uuid"],"getPackageBody":["provider","body"],"readSettings":[],"getGameVersions":[],"searchPackages":["provider","query"],"getUsers":[],"removeUser":["uuid"],"createCluster":["options"],"writeSettings":["setting"],"getPackage":["provider","slug"],"setDefaultUser":["uuid"],"getScreenshots":["id"],"updateClusterById":["id","request"],"getClusters":[],"removeCluster":["id"]}' }
export type Router = { 'oneclient': { openDevTools: () => Promise<void>,
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>> },
'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'core': { getClusters: () => Promise<ClusterModel[]>,
getClusterById: (id: number) => Promise<ClusterModel | null>,
removeCluster: (id: number) => Promise<null>,
createCluster: (options: CreateCluster) => Promise<ClusterModel>,
@ -259,11 +264,6 @@ getMultiplePackages: (provider: Provider, slugs: string[]) => Promise<ManagedPac
getPackageVersions: (provider: Provider, slug: string, mcVersion: string | null, loader: GameLoader | null, offset: number, limit: number) => Promise<Paginated<ManagedVersion>>,
downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: number, skipCompatibility: boolean | null) => Promise<PackageModel>,
getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise<ManagedUser[]> },
'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'oneclient': { openDevTools: () => Promise<void>,
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
openCluster: (folderName: string) => Promise<null> } };

View file

@ -1,16 +1,18 @@
import { useVirtualizer } from '@tanstack/react-virtual';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { useImperativeHandle, useRef, useState } from 'react';
import { useImperativeHandle, useMemo, useState } from 'react';
import styles from './LogViewer.module.css';
export interface LogViewerProps {
content: string;
scrollRef: React.RefObject<HTMLElement | null>;
autoScroll?: boolean;
ref?: React.RefObject<LogViewerRef>;
}
export interface LogViewerRef {
push: (line: string) => void;
// search: (query: string) => void;
}
const LINE_HEIGHT = 16;
@ -19,15 +21,16 @@ const REGEX_PATTERN = /\[((?:\S+ )?\d+:\d+:\d+(?:\.\d+)?)\] \[(.[^(\n\r/\u2028\u
export function LogViewer({
content,
scrollRef,
autoScroll = false,
ref,
}: LogViewerProps) {
const linesRef = useRef<Array<string>>(content.split('\n'));
const lines = useMemo<Array<string>>(() => content.split('\n'), [content]);
// used to force re-render (and re-init of virtualizer hook)
const [lastUpdated, setLastUpdated] = useState(Date.now());
const [_lastUpdated, setLastUpdated] = useState(Date.now());
const virtualizer = useVirtualizer({
count: linesRef.current.length,
count: lines.length,
estimateSize: () => LINE_HEIGHT,
getScrollElement: () => scrollRef.current ?? null,
paddingStart: 4,
@ -37,11 +40,14 @@ export function LogViewer({
});
useImperativeHandle(ref, () => ({
push: (line: string) => {
linesRef.current.push(line);
push: (line) => {
const len = lines.push(line);
setLastUpdated(Date.now());
if (autoScroll)
virtualizer.scrollToIndex(len - 1);
},
}), []);
}), [autoScroll, lines, virtualizer]);
return (
<div
@ -66,7 +72,7 @@ export function LogViewer({
{virtualizer.getVirtualItems().map(virtualRow => (
<LogLine
key={virtualRow.key}
line={linesRef.current[virtualRow.index]}
line={lines[virtualRow.index]}
style={{
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,

View file

@ -1,7 +1,9 @@
import type { QueryClient } from '@tanstack/react-query';
import type { NavigateOptions, ToOptions } from '@tanstack/react-router';
import { TanStackDevtools } from '@tanstack/react-devtools';
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
import { createRootRouteWithContext, Outlet, useRouter } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
import { RouterProvider } from 'react-aria-components';
interface AppRouterContext {
@ -27,12 +29,28 @@ function RootRoute() {
navigate={(to, options) => router.navigate({ to, ...options })}
useHref={to => router.buildLocation({ to }).href}
>
<DevTools />
<div className="h-screen flex flex-col overflow-hidden text-fg-primary">
<Outlet />
<TanStackRouterDevtools />
</div>
</RouterProvider>
// </AnimatedOutletProvider>
);
}
function DevTools() {
return (
<TanStackDevtools
plugins={[
{
name: 'Tanstack Query',
render: <ReactQueryDevtoolsPanel />,
},
{
name: 'Tanstack Router',
render: <TanStackRouterDevtoolsPanel />,
},
]}
/>
);
}

View file

@ -1,11 +1,11 @@
import type { Key } from 'react-aria-components';
import { SheetPage, useSheetPageContext } from '@/components';
import { LoaderContainer, SheetPage, useSheetPageContext } from '@/components';
import { LogViewer } from '@/components/LogViewer';
import { bindings } from '@/main';
import { useCommand, useCommandSuspense } from '@onelauncher/common';
import { getMessageFromError, useCommand, useCommandSuspense } from '@onelauncher/common';
import { Dropdown } from '@onelauncher/common/components';
import { createFileRoute } from '@tanstack/react-router';
import { useEffect, useState } from 'react';
import { useState } from 'react';
export const Route = createFileRoute('/app/cluster/logs')({
component: RouteComponent,
@ -52,8 +52,8 @@ function LogContent({
const { cluster } = Route.useRouteContext();
// TODO: Fix cache
const { data: content } = useCommand(
['getLogByName', cluster.id.toString(), fileName],
const { data: content, error, isLoading } = useCommand(
['getLogByName', cluster.id, fileName],
() => bindings.core.getLogByName(cluster.id, fileName),
{
gcTime: 1000 * 60 * 1, // 1 minute
@ -61,6 +61,8 @@ function LogContent({
);
return (
content && <LogViewer content={content} scrollRef={scrollRef} />
<LoaderContainer loading={isLoading}>
<LogViewer content={getMessageFromError(error) || content || 'Empty log file'} scrollRef={scrollRef} />
</LoaderContainer>
);
}

View file

@ -1,3 +1,4 @@
import type { LauncherError } from '@/bindings.gen';
import type { bindings } from '@/main';
export type BindingCommands = (keyof typeof bindings.core) | (keyof typeof bindings.oneclient);
@ -5,5 +6,6 @@ export type BindingCommands = (keyof typeof bindings.core) | (keyof typeof bindi
declare module '@onelauncher/common' {
interface Register {
commands: Array<BindingCommands>;
defaultError: LauncherError;
}
}

View file

@ -25,5 +25,5 @@
"skipLibCheck": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts"]
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts", "../../../packages/web_common/src/utils/error.ts"]
}

View file

@ -53,6 +53,7 @@ export function Button({
}: ButtonProps) {
return (
<AriaButton
aria-label={props['aria-label'] ?? 'button'}
className={buttonVariants({ color, size, className })}
isDisabled={props.isDisabled}
{...props}

View file

@ -124,27 +124,24 @@ export function Dropdown<TValue extends object>(props: DropdownProps<TValue>) {
selectValue,
triggerClassName,
triggerContentClassName,
'aria-label': ariaLabel,
...rest
} = props;
const styles = dropdownStyles({ minimal, className });
return (
<AriaSelect {...rest} className={styles.root()}>
<AriaSelect aria-label={ariaLabel ?? 'dropdown'} {...rest} className={styles.root()}>
{({ isOpen }) => (
<>
{label && <AriaLabel className={labelClassName}>{label}</AriaLabel>}
{label && <AriaLabel aria-label={label} className={labelClassName}>{label}</AriaLabel>}
<Button className={styles.trigger({ className: triggerClassName })} color="ghost">
<div className={styles.triggerValueContent({ className: triggerContentClassName })}>
{triggerTextPrefix && <span>{triggerTextPrefix}</span>}
{selectValue
? (
<SelectValue>
{selectValue}
</SelectValue>
)
: <SelectValue />}
<SelectValue>
{selectValue}
</SelectValue>
</div>
{
isOpen
@ -153,10 +150,10 @@ export function Dropdown<TValue extends object>(props: DropdownProps<TValue>) {
}
</Button>
<AriaPopover className={twMerge(styles.popover(), popoverClassName)}>
<AriaPopover aria-label="dropdown menu" className={twMerge(styles.popover(), popoverClassName)}>
<div className={styles.popoverContentWrapper()}>
<OverlayScrollbarsComponent className={styles.overlayScrollbars()}>
<AriaListBox className={styles.listBox()}>
<AriaListBox aria-label="dropdown list" className={styles.listBox()}>
{children}
</AriaListBox>
</OverlayScrollbarsComponent>

View file

@ -1,18 +1,31 @@
/* eslint-disable query/exhaustive-deps -- False */
import type { UndefinedInitialDataOptions, UseMutationOptions, UseMutationResult, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult } from '@tanstack/react-query';
import type { AppError } from '../utils/error';
import { useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { isLauncherError } from '../utils/error';
// this gets overwritten by the consumer project
export interface Register {
// commands
// commands: string[];
// defaultError: Error;
}
type RegisterConfig = Register extends {
type CommandKeys = Register extends {
commands: infer Keys;
} ? Keys : never;
type CommandCacheKey = RegisterConfig[number] | (string & {});
type CommandCacheKeys = Array<CommandCacheKey> | [CommandCacheKey] | [false];
type DefaultError = Register extends {
defaultError: infer Error;
} ? Error : never;
type QueryKey = [CommandKeys[number], ...ReadonlyArray<unknown>];
declare module '@tanstack/react-query' {
interface Register {
queryKey: QueryKey;
mutationKey: QueryKey;
}
}
async function fetchCommand<T>(command: () => Promise<T>): Promise<T> {
try {
@ -21,40 +34,40 @@ async function fetchCommand<T>(command: () => Promise<T>): Promise<T> {
}
// TODO: (lynith) handle errors better (requires modifications to TauRPC)
catch (e) {
console.error(e);
if (e instanceof Error)
console.error('[fetch]', e);
if (isLauncherError(e))
return Promise.reject(e);
else
return Promise.reject(new Error('Unknown error'));
return Promise.reject(e);
}
}
export function useCommand<TQueryFnData, TSelected = TQueryFnData>(
cacheKey: CommandCacheKeys,
export function useCommand<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
cacheKey: TQueryKey,
command: () => Promise<TQueryFnData>,
options?: Omit<
UndefinedInitialDataOptions<TQueryFnData, Error, TSelected, CommandCacheKeys | []>,
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
'queryKey' | 'queryFn'
>,
): UseQueryResult<TSelected, Error> {
return useQuery<TQueryFnData, Error, TSelected, CommandCacheKeys | []>({
queryKey: cacheKey[0] === false ? [] : Array.isArray(cacheKey) ? cacheKey : [cacheKey],
): UseQueryResult<TData, TError> {
return useQuery<TQueryFnData, TError, TData, TQueryKey>({
queryKey: cacheKey,
queryFn: () => fetchCommand(command),
enabled: true,
...options,
});
}
export function useCommandSuspense<TQueryFnData, TSelected = TQueryFnData>(
cacheKey: CommandCacheKeys,
export function useCommandSuspense<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
cacheKey: TQueryKey,
command: () => Promise<TQueryFnData>,
options?: Omit<
UseSuspenseQueryOptions<TQueryFnData, Error, TSelected, CommandCacheKeys | []>,
UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'queryKey' | 'queryFn'
>,
): UseSuspenseQueryResult<TSelected, Error> {
return useSuspenseQuery<TQueryFnData, Error, TSelected, CommandCacheKeys | []>({
queryKey: cacheKey[0] === false ? [] : Array.isArray(cacheKey) ? cacheKey : [cacheKey],
): UseSuspenseQueryResult<TData, TError> {
return useSuspenseQuery<TQueryFnData, TError, TData, TQueryKey>({
queryKey: cacheKey,
queryFn: () => fetchCommand(command),
...options,
});
@ -62,7 +75,7 @@ export function useCommandSuspense<TQueryFnData, TSelected = TQueryFnData>(
export function useCommandMut<
TData,
TError = Error,
TError = DefaultError,
TVariables = void,
TContext = unknown,
>(

View file

@ -0,0 +1,23 @@
export interface AppError {
data: string | AppError;
type: string;
}
export function isLauncherError<TErr = AppError>(error: unknown): error is TErr {
return typeof error === 'object'
&& error !== null
&& 'data' in error
&& 'type' in error;
}
export function getMessageFromError<T extends AppError>(error: T): string;
export function getMessageFromError<T extends AppError>(error: T | null | undefined): string | null;
export function getMessageFromError(error: AppError | null | undefined): string | null {
if (!error)
return null;
if (typeof error.data === 'string')
return error.data;
return getMessageFromError(error.data);
}

View file

@ -1,2 +1,3 @@
export * from './dev';
export * from './error';
export * from './nativeExperience';

126
pnpm-lock.yaml generated
View file

@ -27,6 +27,9 @@ catalogs:
'@tanstack/match-sorter-utils':
specifier: ^8.19.4
version: 8.19.4
'@tanstack/react-devtools':
specifier: ^0.4.0
version: 0.4.0
'@tanstack/react-query':
specifier: ^5.66.5
version: 5.75.0
@ -232,6 +235,9 @@ importers:
'@tanstack/match-sorter-utils':
specifier: 'catalog:'
version: 8.19.4
'@tanstack/react-devtools':
specifier: 'catalog:'
version: 0.4.0(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.6)
'@tanstack/react-query':
specifier: 'catalog:'
version: 5.75.0(react@19.1.0)
@ -2480,6 +2486,26 @@ packages:
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'}
'@solid-primitives/event-listener@2.4.3':
resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==}
peerDependencies:
solid-js: ^1.6.12
'@solid-primitives/keyboard@1.3.3':
resolution: {integrity: sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==}
peerDependencies:
solid-js: ^1.6.12
'@solid-primitives/rootless@1.5.2':
resolution: {integrity: sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==}
peerDependencies:
solid-js: ^1.6.12
'@solid-primitives/utils@6.3.2':
resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==}
peerDependencies:
solid-js: ^1.6.12
'@stylistic/eslint-plugin-js@4.2.0':
resolution: {integrity: sha512-MiJr6wvyzMYl/wElmj8Jns8zH7Q1w8XoVtm+WM6yDaTrfxryMyb8n0CMxt82fo42RoLIfxAEtM6tmQVxqhk0/A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -2658,6 +2684,22 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6
'@tanstack/devtools-event-bus@0.2.1':
resolution: {integrity: sha512-JMq3AmrQR2LH9P8Rcj1MTq8Iq/mPk/PyuqSw1L0hO2Wl8G1oz5ue31fS8u8lIgOCVR/mGdJah18p+Pj5OosRJA==}
engines: {node: '>=18'}
'@tanstack/devtools-ui@0.3.0':
resolution: {integrity: sha512-lyP0eM6juIWn8zgI8xI32Lh86gCnjUyNePE9F7Bfgkv5taILmmJAHW5Mme4T2ufv7L8NLwOiBY/bZYnP4zev0w==}
engines: {node: '>=18'}
peerDependencies:
solid-js: '>=1.9.7'
'@tanstack/devtools@0.4.0':
resolution: {integrity: sha512-3LrVUMI/s76XxYbygecOueXPrUf9gDc700iebQrBZ0ZvDmZDB/Umwa84gNx6NAxfQyI24lvB0IqQ8lp970oDSg==}
engines: {node: '>=18'}
peerDependencies:
solid-js: '>=1.9.7'
'@tanstack/eslint-config@0.1.0':
resolution: {integrity: sha512-/lVsQmmezpqqbCFtaxGNkoSnQ/+H0PAHyHF37PGQ6ZBrF8RXktgazMdsD5SCaNT9oEnGCl274GCZ9sY80b50Sg==}
engines: {node: '>=18'}
@ -2681,6 +2723,15 @@ packages:
'@tanstack/query-devtools@5.74.7':
resolution: {integrity: sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==}
'@tanstack/react-devtools@0.4.0':
resolution: {integrity: sha512-QkN0WcbwKjYa107yEYOau+yhaay3XXwL2Ue+V0JxzaBOhvbnFI9tDzvIx7T1NZs8qEUgSjsqmMj/H81cISs2EA==}
engines: {node: '>=18'}
peerDependencies:
'@types/react': '>=16.8'
'@types/react-dom': '>=16.8'
react: '>=16.8'
react-dom: '>=16.8'
'@tanstack/react-query-devtools@5.75.0':
resolution: {integrity: sha512-ogTsCCONHPq8opyeU/NJZlus8+g36KnK+cybC1cs0Knr8iO/wAYgnciz+3DVNq38kRflBb0iJhHkUexp/7WfcQ==}
peerDependencies:
@ -5964,6 +6015,18 @@ packages:
utf-8-validate:
optional: true
ws@8.18.3:
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xml-name-validator@5.0.0:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
@ -8337,6 +8400,27 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {}
'@solid-primitives/event-listener@2.4.3(solid-js@1.9.6)':
dependencies:
'@solid-primitives/utils': 6.3.2(solid-js@1.9.6)
solid-js: 1.9.6
'@solid-primitives/keyboard@1.3.3(solid-js@1.9.6)':
dependencies:
'@solid-primitives/event-listener': 2.4.3(solid-js@1.9.6)
'@solid-primitives/rootless': 1.5.2(solid-js@1.9.6)
'@solid-primitives/utils': 6.3.2(solid-js@1.9.6)
solid-js: 1.9.6
'@solid-primitives/rootless@1.5.2(solid-js@1.9.6)':
dependencies:
'@solid-primitives/utils': 6.3.2(solid-js@1.9.6)
solid-js: 1.9.6
'@solid-primitives/utils@6.3.2(solid-js@1.9.6)':
dependencies:
solid-js: 1.9.6
'@stylistic/eslint-plugin-js@4.2.0(eslint@9.25.1(jiti@2.4.2))':
dependencies:
eslint: 9.25.1(jiti@2.4.2)
@ -8508,6 +8592,33 @@ snapshots:
tailwindcss: 4.1.10
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
'@tanstack/devtools-event-bus@0.2.1':
dependencies:
ws: 8.18.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@tanstack/devtools-ui@0.3.0(csstype@3.1.3)(solid-js@1.9.6)':
dependencies:
goober: 2.1.16(csstype@3.1.3)
solid-js: 1.9.6
transitivePeerDependencies:
- csstype
'@tanstack/devtools@0.4.0(csstype@3.1.3)(solid-js@1.9.6)':
dependencies:
'@solid-primitives/keyboard': 1.3.3(solid-js@1.9.6)
'@tanstack/devtools-event-bus': 0.2.1
'@tanstack/devtools-ui': 0.3.0(csstype@3.1.3)(solid-js@1.9.6)
clsx: 2.1.1
goober: 2.1.16(csstype@3.1.3)
solid-js: 1.9.6
transitivePeerDependencies:
- bufferutil
- csstype
- utf-8-validate
'@tanstack/eslint-config@0.1.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint/js': 9.25.1
@ -8541,6 +8652,19 @@ snapshots:
'@tanstack/query-devtools@5.74.7': {}
'@tanstack/react-devtools@0.4.0(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.6)':
dependencies:
'@tanstack/devtools': 0.4.0(csstype@3.1.3)(solid-js@1.9.6)
'@types/react': 19.1.2
'@types/react-dom': 19.1.3(@types/react@19.1.2)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
transitivePeerDependencies:
- bufferutil
- csstype
- solid-js
- utf-8-validate
'@tanstack/react-query-devtools@5.75.0(@tanstack/react-query@5.75.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/query-devtools': 5.74.7
@ -12439,6 +12563,8 @@ snapshots:
ws@8.18.0:
optional: true
ws@8.18.3: {}
xml-name-validator@5.0.0:
optional: true

View file

@ -61,6 +61,7 @@ catalog:
'@tanstack/react-table': ^8.21.2
'@tanstack/router-plugin': ^1.114.3
'@tanstack/react-virtual': ^3.13.12
'@tanstack/react-devtools': ^0.4.0
# tauri
taurpc: ^1.8.1