mirror of
https://github.com/jakejarvis/stanza.git
synced 2026-10-16 06:15:43 -04:00
refactor: replace hand-rolled debounce with @tanstack/react-pacer and extract useGracePeriod
- Add `@tanstack/react-pacer` and swap both hand-rolled `setTimeout`-based debounce patterns (project-name commit in `project-setup.tsx`, module search fetch in `site-search.tsx`) for `useDebouncedValue`; the search effect now runs directly against the debounced value with an abort controller rather than nesting the fetch inside the timer - Extract `useGracePeriod` from `file-preview.tsx` into `src/hooks/use-grace-period.ts` so it can be reused independently - Hoist `fileSelection`, `lastFileRef`, `activePath`, and `preview` above the reseed effect in `file-preview.tsx` so the effect can reference `defaultPath` and `lastFileRef` without stale-closure issues; expand ancestor directories when a hash-driven selection lands so the highlighted row is visible in the tree
This commit is contained in:
6 files changed
+141
-194
No files matched your search
@@ -23,6 +23,7 @@
|
||||
"@takumi-rs/image-response": "^1.5.1",
|
||||
"@tanstack/react-devtools": "^0.10.5",
|
||||
"@tanstack/react-hotkeys": "^0.10.0",
|
||||
"@tanstack/react-pacer": "^0.22.1",
|
||||
"@tanstack/react-router": "^1.170.8",
|
||||
"@tanstack/react-router-devtools": "^1.167.0",
|
||||
"@tanstack/react-router-ssr-query": "^1.167.0",
|
||||
|
||||
@@ -3,11 +3,12 @@ import { FileTree, useFileTree, useFileTreeSelector } from "@pierre/trees/react"
|
||||
import { IconLoader2 } from "@tabler/icons-react";
|
||||
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
|
||||
import { useTheme } from "@/components/theme-provider";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||
import { useGracePeriod } from "@/hooks/use-grace-period";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import type { Preview } from "@/server/highlighter";
|
||||
|
||||
@@ -33,17 +34,17 @@ const TRUNCATE_FIX_CSS = `
|
||||
display: none !important;
|
||||
}`;
|
||||
|
||||
// Prefer the root `package.json` — best at-a-glance summary of the stack.
|
||||
function defaultPathFor(filePaths: string[]): string | undefined {
|
||||
return filePaths.includes("package.json") ? "package.json" : filePaths[0];
|
||||
}
|
||||
|
||||
// Mirror TanStack Router's `defaultPendingMs` / `defaultPendingMinMs`: wait
|
||||
// before showing a pending indicator (skip the flash for fast loads), and once
|
||||
// shown, keep it visible at least this long (avoid flicker on near-misses).
|
||||
const OVERLAY_PENDING_MS = 300;
|
||||
const OVERLAY_MIN_MS = 250;
|
||||
|
||||
// Prefer the root `package.json` — best at-a-glance summary of the stack.
|
||||
function defaultPathFor(filePaths: string[]): string | undefined {
|
||||
return filePaths.includes("package.json") ? "package.json" : filePaths[0];
|
||||
}
|
||||
|
||||
// "a/b/c.ts" → ["a", "a/b"].
|
||||
function directoryPaths(paths: readonly string[]): string[] {
|
||||
const dirs = new Set<string>();
|
||||
@@ -59,30 +60,6 @@ function directoryPaths(paths: readonly string[]): string[] {
|
||||
return [...dirs];
|
||||
}
|
||||
|
||||
// Defer the truthy edge by `delayMs`, then hold true for at least `minMs`.
|
||||
function useGracePeriod(active: boolean, delayMs: number, minMs: number): boolean {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const shownAtRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (active && !visible) {
|
||||
const t = setTimeout(() => {
|
||||
shownAtRef.current = performance.now();
|
||||
setVisible(true);
|
||||
}, delayMs);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
if (!active && visible) {
|
||||
const remaining = Math.max(0, minMs - (performance.now() - shownAtRef.current));
|
||||
const t = setTimeout(() => setVisible(false), remaining);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
return undefined;
|
||||
}, [active, visible, delayMs, minMs]);
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
export function FilePreview({
|
||||
filePaths,
|
||||
previews,
|
||||
@@ -113,46 +90,7 @@ export function FilePreview({
|
||||
const hashRef = useRef(hash);
|
||||
hashRef.current = hash;
|
||||
const navigate = useNavigate({ from: "/" });
|
||||
|
||||
// Re-seed the model when `filePaths` changes. `resetPaths` collapses
|
||||
// everything and clears selection, so we replay both manually.
|
||||
const prevPathsRef = useRef(filePaths);
|
||||
useEffect(() => {
|
||||
const expandedSet = new Set(
|
||||
directoryPaths(prevPathsRef.current).filter((dir) => {
|
||||
const item = model.getItem(dir);
|
||||
return item != null && "isExpanded" in item && item.isExpanded();
|
||||
}),
|
||||
);
|
||||
// Collapsing a directory leaves its descendants flagged expanded.
|
||||
// `initialExpandedPaths` re-expands the full ancestor chain of every path
|
||||
// given, so replaying such a descendant would re-open the just-collapsed
|
||||
// parent. Keep only dirs whose entire ancestor chain is still expanded.
|
||||
const expanded = [...expandedSet].filter((dir) => {
|
||||
const segments = dir.split("/");
|
||||
segments.pop();
|
||||
let prefix = "";
|
||||
for (const segment of segments) {
|
||||
prefix = prefix ? `${prefix}/${segment}` : segment;
|
||||
if (!expandedSet.has(prefix)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
expanded.sort();
|
||||
const previousSelection = model.getSelectedPaths()[0];
|
||||
model.resetPaths(filePaths, { initialExpandedPaths: expanded });
|
||||
// Prefer the hash (deep link / refresh), then the prior selection, then
|
||||
// the default. The hash is read via ref so its changes don't reseed.
|
||||
const initialHash = hashRef.current;
|
||||
const next =
|
||||
initialHash && filePaths.includes(initialHash)
|
||||
? initialHash
|
||||
: previousSelection != null && filePaths.includes(previousSelection)
|
||||
? previousSelection
|
||||
: defaultPathFor(filePaths);
|
||||
if (next) model.getItem(next)?.select();
|
||||
prevPathsRef.current = filePaths;
|
||||
}, [model, filePaths]);
|
||||
const defaultPath = useMemo(() => defaultPathFor(filePaths), [filePaths]);
|
||||
|
||||
// Project to "current file selection (undefined for nothing/folder)". The
|
||||
// selector pattern lets the tree skip re-rendering us on unrelated state
|
||||
@@ -171,15 +109,64 @@ export function FilePreview({
|
||||
lastFileRef.current = undefined;
|
||||
}
|
||||
if (fileSelection !== undefined) lastFileRef.current = fileSelection;
|
||||
const activePath = lastFileRef.current ?? defaultPathFor(filePaths);
|
||||
const activePath = lastFileRef.current ?? defaultPath;
|
||||
const preview = activePath ? previews[activePath] : undefined;
|
||||
|
||||
// Re-seed the model when `filePaths` changes. `resetPaths` collapses
|
||||
// everything and clears selection, so we replay both manually.
|
||||
const prevPathsRef = useRef(filePaths);
|
||||
useEffect(() => {
|
||||
const expandedSet = new Set(
|
||||
directoryPaths(prevPathsRef.current).filter((dir) => {
|
||||
const item = model.getItem(dir);
|
||||
return item != null && "isExpanded" in item && item.isExpanded();
|
||||
}),
|
||||
);
|
||||
// Collapsing a directory leaves its descendants flagged expanded.
|
||||
// `initialExpandedPaths` re-expands the full ancestor chain of every path
|
||||
// given, so replaying such a descendant would re-open the just-collapsed
|
||||
// parent. Keep only dirs whose entire ancestor chain is still expanded.
|
||||
const preservedExpansion = [...expandedSet].filter((dir) => {
|
||||
const segments = dir.split("/");
|
||||
segments.pop();
|
||||
let prefix = "";
|
||||
for (const segment of segments) {
|
||||
prefix = prefix ? `${prefix}/${segment}` : segment;
|
||||
if (!expandedSet.has(prefix)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// Prefer the hash (deep link / refresh), then the latched last file,
|
||||
// then the default. Hash is read via ref so its changes don't reseed.
|
||||
const initialHash = hashRef.current;
|
||||
const previous = lastFileRef.current;
|
||||
const next =
|
||||
initialHash && filePaths.includes(initialHash)
|
||||
? initialHash
|
||||
: previous && filePaths.includes(previous)
|
||||
? previous
|
||||
: defaultPath;
|
||||
// Reveal the selected file by expanding its ancestor chain — otherwise
|
||||
// the tree selects a row that's collapsed out of view.
|
||||
const expanded = [
|
||||
...new Set([...preservedExpansion, ...(next ? directoryPaths([next]) : [])]),
|
||||
].toSorted();
|
||||
model.resetPaths(filePaths, { initialExpandedPaths: expanded });
|
||||
if (next) model.getItem(next)?.select();
|
||||
prevPathsRef.current = filePaths;
|
||||
}, [model, filePaths, defaultPath]);
|
||||
|
||||
// URL → tree: back/forward (and intra-app links) drop a new hash; reflect
|
||||
// it in the tree. Skipped on the initial reseed since that effect already
|
||||
// honors the hash via `hashRef`.
|
||||
useEffect(() => {
|
||||
if (!hash || !filePaths.includes(hash)) return;
|
||||
if (model.getSelectedPaths()[0] === hash) return;
|
||||
// Expand ancestors so the just-selected row is actually visible.
|
||||
for (const dir of directoryPaths([hash])) {
|
||||
const item = model.getItem(dir);
|
||||
if (item && "expand" in item) item.expand();
|
||||
}
|
||||
model.getItem(hash)?.select();
|
||||
}, [hash, filePaths, model]);
|
||||
|
||||
@@ -189,9 +176,7 @@ export function FilePreview({
|
||||
// cleared after the owning module is deselected).
|
||||
useEffect(() => {
|
||||
const nextHash =
|
||||
activePath && filePaths.includes(activePath) && activePath !== defaultPathFor(filePaths)
|
||||
? activePath
|
||||
: "";
|
||||
activePath && filePaths.includes(activePath) && activePath !== defaultPath ? activePath : "";
|
||||
if (nextHash === hash) return;
|
||||
void navigate({
|
||||
search: (prev) => prev,
|
||||
@@ -200,7 +185,7 @@ export function FilePreview({
|
||||
resetScroll: false,
|
||||
hashScrollIntoView: false,
|
||||
});
|
||||
}, [activePath, hash, filePaths, navigate]);
|
||||
}, [activePath, hash, filePaths, defaultPath, navigate]);
|
||||
|
||||
// Drive the tree's palette from the app theme; otherwise it auto-detects via
|
||||
// `prefers-color-scheme` and mismatches when the user overrides the OS theme.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { validateProjectName } from "@stanza/registry";
|
||||
import { IconAlertCircle } from "@tabler/icons-react";
|
||||
import { useDebouncedValue } from "@tanstack/react-pacer";
|
||||
import type { ChangeEvent } from "react";
|
||||
import { useCallback, useEffect, useEffectEvent, useId, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Field, FieldError } from "@/components/ui/field";
|
||||
@@ -17,10 +18,9 @@ export function ProjectSetup({
|
||||
defaultName: string;
|
||||
onNameChange: (name: string) => void;
|
||||
}) {
|
||||
// Debounce the upward push (navigates → reruns the loader) while keeping the
|
||||
// field responsive. `lastSyncedRef` ignores the echo of our own pushes —
|
||||
// otherwise `setDraft(name)` would clobber characters the user typed between
|
||||
// the debounce firing and the new `name` prop arriving.
|
||||
// `lastSyncedRef` ignores the echo of our own pushes — otherwise
|
||||
// `setDraft(name)` would clobber characters the user typed between the
|
||||
// debounce firing and the new `name` prop arriving.
|
||||
const [draft, setDraft] = useState(name);
|
||||
const lastSyncedRef = useRef(name);
|
||||
|
||||
@@ -38,19 +38,14 @@ export function ProjectSetup({
|
||||
// shouldn't render a "required" error.
|
||||
const showError = !validation.ok && draft.trim().length > 0;
|
||||
|
||||
// Read the latest `onNameChange` without making the debounce effect re-run
|
||||
// every time the parent rebuilds the callback.
|
||||
const commit = useEffectEvent((next: string) => {
|
||||
lastSyncedRef.current = next;
|
||||
onNameChange(next);
|
||||
});
|
||||
const [debouncedDraft] = useDebouncedValue(draft, { wait: 300 });
|
||||
|
||||
useEffect(() => {
|
||||
if (draft === lastSyncedRef.current) return undefined;
|
||||
if (!validation.ok) return undefined;
|
||||
const timer = setTimeout(() => commit(draft), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [draft, validation.ok]);
|
||||
if (debouncedDraft === lastSyncedRef.current) return;
|
||||
if (!validateProjectName(debouncedDraft).ok) return;
|
||||
lastSyncedRef.current = debouncedDraft;
|
||||
onNameChange(debouncedDraft);
|
||||
}, [debouncedDraft, onNameChange]);
|
||||
|
||||
const onDraftChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => setDraft(e.target.value),
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ModuleSummary, RegistryIndex } from "@stanza/registry";
|
||||
import { categoryLabel } from "@stanza/registry";
|
||||
import { IconBookmark, IconSearch } from "@tabler/icons-react";
|
||||
import { formatForDisplay, useHotkey } from "@tanstack/react-hotkeys";
|
||||
import { useDebouncedValue } from "@tanstack/react-pacer";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import type { SortedResult } from "fumadocs-core/search";
|
||||
import { useDocsSearch } from "fumadocs-core/search/client";
|
||||
@@ -76,16 +77,19 @@ export function SiteSearch({ registry, docs }: { registry: RegistryIndex; docs:
|
||||
docsSearch.setSearch(query);
|
||||
}, [query, docsSearch]);
|
||||
|
||||
// Modules: small hand-rolled debounced fetch (not worth abstracting).
|
||||
// Modules: debounce the query, then fetch with an abort signal for any
|
||||
// request that's still in-flight when the debounced value changes again.
|
||||
const [moduleResults, setModuleResults] = useState<ModuleSummary[] | null>(null);
|
||||
const [debouncedQuery] = useDebouncedValue(query, { wait: DEBOUNCE_MS });
|
||||
useEffect(() => {
|
||||
if (!query.trim()) {
|
||||
if (!debouncedQuery.trim()) {
|
||||
setModuleResults(null);
|
||||
return () => {};
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => {
|
||||
fetch(`/api/search/modules?q=${encodeURIComponent(query)}`, { signal: controller.signal })
|
||||
fetch(`/api/search/modules?q=${encodeURIComponent(debouncedQuery)}`, {
|
||||
signal: controller.signal,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data: unknown) => setModuleResults(parseModuleResults(data)))
|
||||
.catch((err: unknown) => {
|
||||
@@ -94,12 +98,8 @@ export function SiteSearch({ registry, docs }: { registry: RegistryIndex; docs:
|
||||
// the dialog or throwing past React's render loop.
|
||||
setModuleResults([]);
|
||||
});
|
||||
}, DEBOUNCE_MS);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
controller.abort();
|
||||
};
|
||||
}, [query]);
|
||||
return () => controller.abort();
|
||||
}, [debouncedQuery]);
|
||||
|
||||
const pageTitlesByUrl = useMemo(
|
||||
() => new Map(docs.pages.map((p) => [p.url, p.title])),
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
/**
|
||||
* Defer the truthy edge by `delayMs`, then hold true for at least `minMs`.
|
||||
*
|
||||
* Mirrors TanStack Router's `defaultPendingMs` / `defaultPendingMinMs`: wait
|
||||
* before showing a pending indicator (skip the flash for fast loads), and once
|
||||
* shown, keep it visible at least this long (avoid flicker on near-misses).
|
||||
*/
|
||||
export function useGracePeriod(active: boolean, delayMs: number, minMs: number): boolean {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const shownAtRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (active && !visible) {
|
||||
const t = setTimeout(() => {
|
||||
shownAtRef.current = performance.now();
|
||||
setVisible(true);
|
||||
}, delayMs);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
if (!active && visible) {
|
||||
const remaining = Math.max(0, minMs - (performance.now() - shownAtRef.current));
|
||||
const t = setTimeout(() => setVisible(false), remaining);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
return undefined;
|
||||
}, [active, visible, delayMs, minMs]);
|
||||
|
||||
return visible;
|
||||
}
|
||||
Generated
+28
-93
@@ -121,6 +121,9 @@ importers:
|
||||
'@tanstack/react-hotkeys':
|
||||
specifier: ^0.10.0
|
||||
version: 0.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
'@tanstack/react-pacer':
|
||||
specifier: ^0.22.1
|
||||
version: 0.22.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
'@tanstack/react-router':
|
||||
specifier: ^1.170.8
|
||||
version: 1.170.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
@@ -241,10 +244,10 @@ importers:
|
||||
version: '@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3)'
|
||||
vite-plus:
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)
|
||||
version: 0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))
|
||||
vitest:
|
||||
specifier: npm:@voidzero-dev/vite-plus-test@^0.1.22
|
||||
version: '@voidzero-dev/vite-plus-test@0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)'
|
||||
version: '@voidzero-dev/vite-plus-test@0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))'
|
||||
web-vitals:
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0
|
||||
@@ -2014,6 +2017,10 @@ packages:
|
||||
resolution: {integrity: sha512-vqH7X9nb0MTJ/O08++dB5bP9jgj4+BIPOUu/U+6myG86lDsirZSVSobpq5UQpE7nBuk62i8eIYeOhd+OMl/UrA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@tanstack/pacer@0.21.1':
|
||||
resolution: {integrity: sha512-hB01dd4rlsYcTCNP7wK186jgAe6K5qimgM1Y5Jtvz+9PUaILvpmeLLjmQNUNSO1l23lIt+CeQR6mO1mjlPvRtQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@tanstack/query-core@5.100.14':
|
||||
resolution: {integrity: sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==}
|
||||
|
||||
@@ -2033,6 +2040,13 @@ packages:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
|
||||
'@tanstack/react-pacer@0.22.1':
|
||||
resolution: {integrity: sha512-CenQqK0GluSPIrnsG1yuD7w5uMSQ/4lI9AcGEFxBrRd66r260boWcYRIsS5+eHtXb238FoZYhKmJPGlhRzmHRw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
|
||||
'@tanstack/react-query@5.100.14':
|
||||
resolution: {integrity: sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==}
|
||||
peerDependencies:
|
||||
@@ -6646,6 +6660,11 @@ snapshots:
|
||||
dependencies:
|
||||
'@tanstack/store': 0.11.0
|
||||
|
||||
'@tanstack/pacer@0.21.1':
|
||||
dependencies:
|
||||
'@tanstack/devtools-event-client': 0.4.3
|
||||
'@tanstack/store': 0.11.0
|
||||
|
||||
'@tanstack/query-core@5.100.14': {}
|
||||
|
||||
'@tanstack/react-devtools@0.10.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(csstype@3.2.3)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(solid-js@1.9.13)':
|
||||
@@ -6668,6 +6687,13 @@ snapshots:
|
||||
react: 19.2.6
|
||||
react-dom: 19.2.6(react@19.2.6)
|
||||
|
||||
'@tanstack/react-pacer@0.22.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
|
||||
dependencies:
|
||||
'@tanstack/pacer': 0.21.1
|
||||
'@tanstack/react-store': 0.11.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||
react: 19.2.6
|
||||
react-dom: 19.2.6(react@19.2.6)
|
||||
|
||||
'@tanstack/react-query@5.100.14(react@19.2.6)':
|
||||
dependencies:
|
||||
'@tanstack/query-core': 5.100.14
|
||||
@@ -7076,48 +7102,6 @@ snapshots:
|
||||
'@voidzero-dev/vite-plus-linux-x64-musl@0.1.22':
|
||||
optional: true
|
||||
|
||||
'@voidzero-dev/vite-plus-test@0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@voidzero-dev/vite-plus-core': 0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3)
|
||||
es-module-lexer: 1.7.0
|
||||
obug: 2.1.1
|
||||
pixelmatch: 7.2.0
|
||||
pngjs: 7.0.0
|
||||
sirv: 3.0.2
|
||||
std-env: 4.1.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 1.2.2
|
||||
tinyglobby: 0.2.16
|
||||
vite: '@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3)'
|
||||
ws: 8.21.0
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.1
|
||||
'@types/node': 25.9.1
|
||||
jsdom: 29.1.1(@noble/hashes@1.8.0)
|
||||
transitivePeerDependencies:
|
||||
- '@arethetypeswrong/core'
|
||||
- '@tsdown/css'
|
||||
- '@tsdown/exe'
|
||||
- '@vitejs/devtools'
|
||||
- bufferutil
|
||||
- esbuild
|
||||
- jiti
|
||||
- less
|
||||
- publint
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- unplugin-unused
|
||||
- unrun
|
||||
- utf-8-validate
|
||||
- yaml
|
||||
|
||||
'@voidzero-dev/vite-plus-test@0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
@@ -9973,55 +9957,6 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
vite-plus@0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.129.0
|
||||
'@oxlint/plugins': 1.61.0
|
||||
'@voidzero-dev/vite-plus-core': 0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3)
|
||||
'@voidzero-dev/vite-plus-test': 0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)
|
||||
oxfmt: 0.48.0
|
||||
oxlint: 1.63.0(oxlint-tsgolint@0.22.1)
|
||||
oxlint-tsgolint: 0.22.1
|
||||
optionalDependencies:
|
||||
'@voidzero-dev/vite-plus-darwin-arm64': 0.1.22
|
||||
'@voidzero-dev/vite-plus-darwin-x64': 0.1.22
|
||||
'@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.22
|
||||
'@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.22
|
||||
'@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.22
|
||||
'@voidzero-dev/vite-plus-linux-x64-musl': 0.1.22
|
||||
'@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.22
|
||||
'@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.22
|
||||
transitivePeerDependencies:
|
||||
- '@arethetypeswrong/core'
|
||||
- '@edge-runtime/vm'
|
||||
- '@opentelemetry/api'
|
||||
- '@tsdown/css'
|
||||
- '@tsdown/exe'
|
||||
- '@types/node'
|
||||
- '@vitejs/devtools'
|
||||
- '@vitest/coverage-istanbul'
|
||||
- '@vitest/coverage-v8'
|
||||
- '@vitest/ui'
|
||||
- bufferutil
|
||||
- esbuild
|
||||
- happy-dom
|
||||
- jiti
|
||||
- jsdom
|
||||
- less
|
||||
- publint
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- unplugin-unused
|
||||
- unrun
|
||||
- utf-8-validate
|
||||
- vite
|
||||
- yaml
|
||||
|
||||
vite-plus@0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)):
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.129.0
|
||||
|
||||
Reference in new issue
Block a user