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
+149
-202
No files matched your search
@@ -23,6 +23,7 @@
|
|||||||
"@takumi-rs/image-response": "^1.5.1",
|
"@takumi-rs/image-response": "^1.5.1",
|
||||||
"@tanstack/react-devtools": "^0.10.5",
|
"@tanstack/react-devtools": "^0.10.5",
|
||||||
"@tanstack/react-hotkeys": "^0.10.0",
|
"@tanstack/react-hotkeys": "^0.10.0",
|
||||||
|
"@tanstack/react-pacer": "^0.22.1",
|
||||||
"@tanstack/react-router": "^1.170.8",
|
"@tanstack/react-router": "^1.170.8",
|
||||||
"@tanstack/react-router-devtools": "^1.167.0",
|
"@tanstack/react-router-devtools": "^1.167.0",
|
||||||
"@tanstack/react-router-ssr-query": "^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 { IconLoader2 } from "@tabler/icons-react";
|
||||||
import { useLocation, useNavigate } from "@tanstack/react-router";
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
import type { ReactNode } from "react";
|
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 { useTheme } from "@/components/theme-provider";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||||
|
import { useGracePeriod } from "@/hooks/use-grace-period";
|
||||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||||
import type { Preview } from "@/server/highlighter";
|
import type { Preview } from "@/server/highlighter";
|
||||||
|
|
||||||
@@ -33,17 +34,17 @@ const TRUNCATE_FIX_CSS = `
|
|||||||
display: none !important;
|
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
|
// Mirror TanStack Router's `defaultPendingMs` / `defaultPendingMinMs`: wait
|
||||||
// before showing a pending indicator (skip the flash for fast loads), and once
|
// 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).
|
// shown, keep it visible at least this long (avoid flicker on near-misses).
|
||||||
const OVERLAY_PENDING_MS = 300;
|
const OVERLAY_PENDING_MS = 300;
|
||||||
const OVERLAY_MIN_MS = 250;
|
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"].
|
// "a/b/c.ts" → ["a", "a/b"].
|
||||||
function directoryPaths(paths: readonly string[]): string[] {
|
function directoryPaths(paths: readonly string[]): string[] {
|
||||||
const dirs = new Set<string>();
|
const dirs = new Set<string>();
|
||||||
@@ -59,30 +60,6 @@ function directoryPaths(paths: readonly string[]): string[] {
|
|||||||
return [...dirs];
|
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({
|
export function FilePreview({
|
||||||
filePaths,
|
filePaths,
|
||||||
previews,
|
previews,
|
||||||
@@ -113,46 +90,7 @@ export function FilePreview({
|
|||||||
const hashRef = useRef(hash);
|
const hashRef = useRef(hash);
|
||||||
hashRef.current = hash;
|
hashRef.current = hash;
|
||||||
const navigate = useNavigate({ from: "/" });
|
const navigate = useNavigate({ from: "/" });
|
||||||
|
const defaultPath = useMemo(() => defaultPathFor(filePaths), [filePaths]);
|
||||||
// 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]);
|
|
||||||
|
|
||||||
// Project to "current file selection (undefined for nothing/folder)". The
|
// Project to "current file selection (undefined for nothing/folder)". The
|
||||||
// selector pattern lets the tree skip re-rendering us on unrelated state
|
// selector pattern lets the tree skip re-rendering us on unrelated state
|
||||||
@@ -171,15 +109,64 @@ export function FilePreview({
|
|||||||
lastFileRef.current = undefined;
|
lastFileRef.current = undefined;
|
||||||
}
|
}
|
||||||
if (fileSelection !== undefined) lastFileRef.current = fileSelection;
|
if (fileSelection !== undefined) lastFileRef.current = fileSelection;
|
||||||
const activePath = lastFileRef.current ?? defaultPathFor(filePaths);
|
const activePath = lastFileRef.current ?? defaultPath;
|
||||||
const preview = activePath ? previews[activePath] : undefined;
|
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
|
// 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
|
// it in the tree. Skipped on the initial reseed since that effect already
|
||||||
// honors the hash via `hashRef`.
|
// honors the hash via `hashRef`.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hash || !filePaths.includes(hash)) return;
|
if (!hash || !filePaths.includes(hash)) return;
|
||||||
if (model.getSelectedPaths()[0] === 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();
|
model.getItem(hash)?.select();
|
||||||
}, [hash, filePaths, model]);
|
}, [hash, filePaths, model]);
|
||||||
|
|
||||||
@@ -189,9 +176,7 @@ export function FilePreview({
|
|||||||
// cleared after the owning module is deselected).
|
// cleared after the owning module is deselected).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nextHash =
|
const nextHash =
|
||||||
activePath && filePaths.includes(activePath) && activePath !== defaultPathFor(filePaths)
|
activePath && filePaths.includes(activePath) && activePath !== defaultPath ? activePath : "";
|
||||||
? activePath
|
|
||||||
: "";
|
|
||||||
if (nextHash === hash) return;
|
if (nextHash === hash) return;
|
||||||
void navigate({
|
void navigate({
|
||||||
search: (prev) => prev,
|
search: (prev) => prev,
|
||||||
@@ -200,7 +185,7 @@ export function FilePreview({
|
|||||||
resetScroll: false,
|
resetScroll: false,
|
||||||
hashScrollIntoView: 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
|
// 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.
|
// `prefers-color-scheme` and mismatches when the user overrides the OS theme.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { validateProjectName } from "@stanza/registry";
|
import { validateProjectName } from "@stanza/registry";
|
||||||
import { IconAlertCircle } from "@tabler/icons-react";
|
import { IconAlertCircle } from "@tabler/icons-react";
|
||||||
|
import { useDebouncedValue } from "@tanstack/react-pacer";
|
||||||
import type { ChangeEvent } from "react";
|
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 { Card } from "@/components/ui/card";
|
||||||
import { Field, FieldError } from "@/components/ui/field";
|
import { Field, FieldError } from "@/components/ui/field";
|
||||||
@@ -17,10 +18,9 @@ export function ProjectSetup({
|
|||||||
defaultName: string;
|
defaultName: string;
|
||||||
onNameChange: (name: string) => void;
|
onNameChange: (name: string) => void;
|
||||||
}) {
|
}) {
|
||||||
// Debounce the upward push (navigates → reruns the loader) while keeping the
|
// `lastSyncedRef` ignores the echo of our own pushes — otherwise
|
||||||
// field responsive. `lastSyncedRef` ignores the echo of our own pushes —
|
// `setDraft(name)` would clobber characters the user typed between the
|
||||||
// otherwise `setDraft(name)` would clobber characters the user typed between
|
// debounce firing and the new `name` prop arriving.
|
||||||
// the debounce firing and the new `name` prop arriving.
|
|
||||||
const [draft, setDraft] = useState(name);
|
const [draft, setDraft] = useState(name);
|
||||||
const lastSyncedRef = useRef(name);
|
const lastSyncedRef = useRef(name);
|
||||||
|
|
||||||
@@ -38,19 +38,14 @@ export function ProjectSetup({
|
|||||||
// shouldn't render a "required" error.
|
// shouldn't render a "required" error.
|
||||||
const showError = !validation.ok && draft.trim().length > 0;
|
const showError = !validation.ok && draft.trim().length > 0;
|
||||||
|
|
||||||
// Read the latest `onNameChange` without making the debounce effect re-run
|
const [debouncedDraft] = useDebouncedValue(draft, { wait: 300 });
|
||||||
// every time the parent rebuilds the callback.
|
|
||||||
const commit = useEffectEvent((next: string) => {
|
|
||||||
lastSyncedRef.current = next;
|
|
||||||
onNameChange(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draft === lastSyncedRef.current) return undefined;
|
if (debouncedDraft === lastSyncedRef.current) return;
|
||||||
if (!validation.ok) return undefined;
|
if (!validateProjectName(debouncedDraft).ok) return;
|
||||||
const timer = setTimeout(() => commit(draft), 300);
|
lastSyncedRef.current = debouncedDraft;
|
||||||
return () => clearTimeout(timer);
|
onNameChange(debouncedDraft);
|
||||||
}, [draft, validation.ok]);
|
}, [debouncedDraft, onNameChange]);
|
||||||
|
|
||||||
const onDraftChange = useCallback(
|
const onDraftChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => setDraft(e.target.value),
|
(e: ChangeEvent<HTMLInputElement>) => setDraft(e.target.value),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { ModuleSummary, RegistryIndex } from "@stanza/registry";
|
|||||||
import { categoryLabel } from "@stanza/registry";
|
import { categoryLabel } from "@stanza/registry";
|
||||||
import { IconBookmark, IconSearch } from "@tabler/icons-react";
|
import { IconBookmark, IconSearch } from "@tabler/icons-react";
|
||||||
import { formatForDisplay, useHotkey } from "@tanstack/react-hotkeys";
|
import { formatForDisplay, useHotkey } from "@tanstack/react-hotkeys";
|
||||||
|
import { useDebouncedValue } from "@tanstack/react-pacer";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import type { SortedResult } from "fumadocs-core/search";
|
import type { SortedResult } from "fumadocs-core/search";
|
||||||
import { useDocsSearch } from "fumadocs-core/search/client";
|
import { useDocsSearch } from "fumadocs-core/search/client";
|
||||||
@@ -76,30 +77,29 @@ export function SiteSearch({ registry, docs }: { registry: RegistryIndex; docs:
|
|||||||
docsSearch.setSearch(query);
|
docsSearch.setSearch(query);
|
||||||
}, [query, docsSearch]);
|
}, [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 [moduleResults, setModuleResults] = useState<ModuleSummary[] | null>(null);
|
||||||
|
const [debouncedQuery] = useDebouncedValue(query, { wait: DEBOUNCE_MS });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!query.trim()) {
|
if (!debouncedQuery.trim()) {
|
||||||
setModuleResults(null);
|
setModuleResults(null);
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timer = setTimeout(() => {
|
fetch(`/api/search/modules?q=${encodeURIComponent(debouncedQuery)}`, {
|
||||||
fetch(`/api/search/modules?q=${encodeURIComponent(query)}`, { signal: controller.signal })
|
signal: controller.signal,
|
||||||
.then((res) => res.json())
|
})
|
||||||
.then((data: unknown) => setModuleResults(parseModuleResults(data)))
|
.then((res) => res.json())
|
||||||
.catch((err: unknown) => {
|
.then((data: unknown) => setModuleResults(parseModuleResults(data)))
|
||||||
if (err instanceof DOMException && err.name === "AbortError") return;
|
.catch((err: unknown) => {
|
||||||
// Surface network/parse failures as "no matches" rather than blanking
|
if (err instanceof DOMException && err.name === "AbortError") return;
|
||||||
// the dialog or throwing past React's render loop.
|
// Surface network/parse failures as "no matches" rather than blanking
|
||||||
setModuleResults([]);
|
// the dialog or throwing past React's render loop.
|
||||||
});
|
setModuleResults([]);
|
||||||
}, DEBOUNCE_MS);
|
});
|
||||||
return () => {
|
return () => controller.abort();
|
||||||
clearTimeout(timer);
|
}, [debouncedQuery]);
|
||||||
controller.abort();
|
|
||||||
};
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
const pageTitlesByUrl = useMemo(
|
const pageTitlesByUrl = useMemo(
|
||||||
() => new Map(docs.pages.map((p) => [p.url, p.title])),
|
() => 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':
|
'@tanstack/react-hotkeys':
|
||||||
specifier: ^0.10.0
|
specifier: ^0.10.0
|
||||||
version: 0.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
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':
|
'@tanstack/react-router':
|
||||||
specifier: ^1.170.8
|
specifier: ^1.170.8
|
||||||
version: 1.170.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
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)'
|
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:
|
vite-plus:
|
||||||
specifier: 'catalog:'
|
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:
|
vitest:
|
||||||
specifier: npm:@voidzero-dev/vite-plus-test@^0.1.22
|
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:
|
web-vitals:
|
||||||
specifier: ^5.2.0
|
specifier: ^5.2.0
|
||||||
version: 5.2.0
|
version: 5.2.0
|
||||||
@@ -2014,6 +2017,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-vqH7X9nb0MTJ/O08++dB5bP9jgj4+BIPOUu/U+6myG86lDsirZSVSobpq5UQpE7nBuk62i8eIYeOhd+OMl/UrA==}
|
resolution: {integrity: sha512-vqH7X9nb0MTJ/O08++dB5bP9jgj4+BIPOUu/U+6myG86lDsirZSVSobpq5UQpE7nBuk62i8eIYeOhd+OMl/UrA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@tanstack/pacer@0.21.1':
|
||||||
|
resolution: {integrity: sha512-hB01dd4rlsYcTCNP7wK186jgAe6K5qimgM1Y5Jtvz+9PUaILvpmeLLjmQNUNSO1l23lIt+CeQR6mO1mjlPvRtQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@tanstack/query-core@5.100.14':
|
'@tanstack/query-core@5.100.14':
|
||||||
resolution: {integrity: sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==}
|
resolution: {integrity: sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==}
|
||||||
|
|
||||||
@@ -2033,6 +2040,13 @@ packages:
|
|||||||
react: '>=16.8'
|
react: '>=16.8'
|
||||||
react-dom: '>=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':
|
'@tanstack/react-query@5.100.14':
|
||||||
resolution: {integrity: sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==}
|
resolution: {integrity: sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6646,6 +6660,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/store': 0.11.0
|
'@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/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)':
|
'@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: 19.2.6
|
||||||
react-dom: 19.2.6(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)':
|
'@tanstack/react-query@5.100.14(react@19.2.6)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/query-core': 5.100.14
|
'@tanstack/query-core': 5.100.14
|
||||||
@@ -7076,48 +7102,6 @@ snapshots:
|
|||||||
'@voidzero-dev/vite-plus-linux-x64-musl@0.1.22':
|
'@voidzero-dev/vite-plus-linux-x64-musl@0.1.22':
|
||||||
optional: true
|
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))':
|
'@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:
|
dependencies:
|
||||||
'@standard-schema/spec': 1.1.0
|
'@standard-schema/spec': 1.1.0
|
||||||
@@ -9973,55 +9957,6 @@ snapshots:
|
|||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
vfile-message: 4.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)):
|
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:
|
dependencies:
|
||||||
'@oxc-project/types': 0.129.0
|
'@oxc-project/types': 0.129.0
|
||||||
|
|||||||
Reference in new issue
Block a user