From 724eaee77e043cf55a2586c74a92ed9bb2744b37 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 13 Mar 2026 12:52:19 +0800 Subject: [PATCH 01/37] chore: add dev proxy server, update deps (#33371) --- .github/workflows/web-tests.yml | 2 +- web/.env.example | 5 + .../base/avatar/__tests__/index.spec.tsx | 13 +- .../__tests__/chat-wrapper.spec.tsx | 11 +- .../__tests__/chat-wrapper.spec.tsx | 4 +- .../base/mermaid/__tests__/index.spec.tsx | 148 +-- .../base/segmented-control/index.tsx | 3 +- web/app/styles/globals.css | 1 + web/eslint-suppressions.json | 5 - web/knip.config.ts | 6 +- web/package.json | 46 +- web/plugins/dev-proxy/cookies.ts | 98 ++ web/plugins/dev-proxy/server.spec.ts | 113 ++ web/plugins/dev-proxy/server.ts | 202 ++++ web/pnpm-lock.yaml | 1007 +++++++++-------- web/scripts/dev-hono-proxy.ts | 21 + web/taze.config.js | 19 + web/vite.config.ts | 9 +- 18 files changed, 1075 insertions(+), 638 deletions(-) create mode 100644 web/plugins/dev-proxy/cookies.ts create mode 100644 web/plugins/dev-proxy/server.spec.ts create mode 100644 web/plugins/dev-proxy/server.ts create mode 100644 web/scripts/dev-hono-proxy.ts create mode 100644 web/taze.config.js diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 33e9170b02..7d2977245f 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -72,7 +72,7 @@ jobs: merge-multiple: true - name: Merge reports - run: pnpm vitest --merge-reports --coverage --silent=passed-only + run: pnpm vitest --merge-reports --reporter=json --reporter=agent --coverage - name: Coverage Summary if: always() diff --git a/web/.env.example b/web/.env.example index df4e725c51..ed06ebe2c9 100644 --- a/web/.env.example +++ b/web/.env.example @@ -12,6 +12,11 @@ NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api # console or api domain. # example: http://udify.app/api NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api +# Dev-only Hono proxy targets. The frontend keeps requesting http://localhost:5001 directly. +HONO_PROXY_HOST=127.0.0.1 +HONO_PROXY_PORT=5001 +HONO_CONSOLE_API_PROXY_TARGET= +HONO_PUBLIC_API_PROXY_TARGET= # When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1. NEXT_PUBLIC_COOKIE_DOMAIN= diff --git a/web/app/components/base/avatar/__tests__/index.spec.tsx b/web/app/components/base/avatar/__tests__/index.spec.tsx index f7fae7105d..69c56ac993 100644 --- a/web/app/components/base/avatar/__tests__/index.spec.tsx +++ b/web/app/components/base/avatar/__tests__/index.spec.tsx @@ -3,12 +3,10 @@ import { Avatar } from '../index' describe('Avatar', () => { describe('Rendering', () => { - it('should render img element when avatar URL is provided', () => { + it('should keep the fallback visible when avatar URL is provided before image load', () => { render() - const img = screen.getByRole('img', { name: 'John Doe' }) - expect(img).toBeInTheDocument() - expect(img).toHaveAttribute('src', 'https://example.com/avatar.jpg') + expect(screen.getByText('J')).toBeInTheDocument() }) it('should render fallback with uppercase initial when avatar is null', () => { @@ -18,10 +16,9 @@ describe('Avatar', () => { expect(screen.getByText('A')).toBeInTheDocument() }) - it('should render both image and fallback when avatar is provided', () => { + it('should render the fallback when avatar is provided', () => { render() - expect(screen.getByRole('img')).toBeInTheDocument() expect(screen.getByText('J')).toBeInTheDocument() }) }) @@ -90,7 +87,7 @@ describe('Avatar', () => { }) describe('onLoadingStatusChange', () => { - it('should render image when avatar and onLoadingStatusChange are provided', () => { + it('should render the fallback when avatar and onLoadingStatusChange are provided', () => { render( { />, ) - expect(screen.getByRole('img')).toBeInTheDocument() + expect(screen.getByText('J')).toBeInTheDocument() }) it('should not render image when avatar is null even with onLoadingStatusChange', () => { diff --git a/web/app/components/base/chat/chat-with-history/__tests__/chat-wrapper.spec.tsx b/web/app/components/base/chat/chat-with-history/__tests__/chat-wrapper.spec.tsx index 84d7b5f2dd..60a5da5d49 100644 --- a/web/app/components/base/chat/chat-with-history/__tests__/chat-wrapper.spec.tsx +++ b/web/app/components/base/chat/chat-with-history/__tests__/chat-wrapper.spec.tsx @@ -978,7 +978,7 @@ describe('ChatWrapper', () => { expect(screen.getByAltText('answer icon')).toBeInTheDocument() }) - it('should render question icon when user avatar is available', () => { + it('should render question icon fallback when user avatar is available', () => { vi.mocked(useChatWithHistoryContext).mockReturnValue({ ...defaultContextValue, initUserVariables: { @@ -992,12 +992,11 @@ describe('ChatWrapper', () => { chatList: [{ id: 'q1', content: 'Question' }], } as unknown as ChatHookReturn) - const { container } = render() - const avatar = container.querySelector('img[alt="John Doe"]') - expect(avatar).toBeInTheDocument() + render() + expect(screen.getByText('J')).toBeInTheDocument() }) - it('should use fallback values for nullable appData, appMeta and user name', () => { + it('should use fallback values for nullable appData, appMeta and avatar name', () => { vi.mocked(useChatWithHistoryContext).mockReturnValue({ ...defaultContextValue, appData: null as unknown as AppData, @@ -1014,7 +1013,7 @@ describe('ChatWrapper', () => { render() expect(screen.getByText('Question with fallback avatar name')).toBeInTheDocument() - expect(screen.getByAltText('user')).toBeInTheDocument() + expect(screen.getByText('U')).toBeInTheDocument() }) it('should set handleStop on currentChatInstanceRef', () => { diff --git a/web/app/components/base/chat/embedded-chatbot/__tests__/chat-wrapper.spec.tsx b/web/app/components/base/chat/embedded-chatbot/__tests__/chat-wrapper.spec.tsx index 6fbda2c702..865023722e 100644 --- a/web/app/components/base/chat/embedded-chatbot/__tests__/chat-wrapper.spec.tsx +++ b/web/app/components/base/chat/embedded-chatbot/__tests__/chat-wrapper.spec.tsx @@ -327,7 +327,7 @@ describe('EmbeddedChatbot chat-wrapper', () => { expect(screen.getByRole('button', { name: 'send message' })).toBeDisabled() }) - it('should show the user name when avatar data is provided', () => { + it('should show the user avatar fallback when avatar data is provided', () => { vi.mocked(useEmbeddedChatbotContext).mockReturnValue(createContextValue({ initUserVariables: { avatar_url: 'https://example.com/avatar.png', @@ -337,7 +337,7 @@ describe('EmbeddedChatbot chat-wrapper', () => { render() - expect(screen.getByRole('img', { name: 'Alice' })).toBeInTheDocument() + expect(screen.getByText('A')).toBeInTheDocument() }) }) diff --git a/web/app/components/base/mermaid/__tests__/index.spec.tsx b/web/app/components/base/mermaid/__tests__/index.spec.tsx index c2d616bf4f..310f346083 100644 --- a/web/app/components/base/mermaid/__tests__/index.spec.tsx +++ b/web/app/components/base/mermaid/__tests__/index.spec.tsx @@ -639,128 +639,50 @@ describe('Mermaid Flowchart Component Module Isolation', () => { } }) - it('should tolerate missing hidden container during classic render and cleanup', async () => { - vi.resetModules() - let pendingContainerRef: unknown | null = null - let patchedContainerRef = false - let patchedTimeoutRef = false - let containerReadCount = 0 - const virtualContainer = { innerHTML: 'seed' } as HTMLDivElement - - vi.doMock('react', async () => { - const reactActual = await vi.importActual('react') - const mockedUseRef = ((initialValue: unknown) => { - const ref = reactActual.useRef(initialValue as never) - if (!patchedContainerRef && initialValue === null) - pendingContainerRef = ref - - if (!patchedContainerRef - && pendingContainerRef - && typeof initialValue === 'string' - && initialValue.startsWith('mermaid-chart-')) { - Object.defineProperty(pendingContainerRef as { current: unknown }, 'current', { - configurable: true, - get() { - containerReadCount += 1 - if (containerReadCount === 1) - return virtualContainer - return null - }, - set(_value: HTMLDivElement | null) { }, - }) - patchedContainerRef = true - pendingContainerRef = null - } - - if (patchedContainerRef && !patchedTimeoutRef && initialValue === undefined) { - patchedTimeoutRef = true - Object.defineProperty(ref, 'current', { - configurable: true, - get() { - return undefined - }, - set(_value: NodeJS.Timeout | undefined) { }, - }) - return ref - } - - return ref - }) as typeof reactActual.useRef - - return { - ...reactActual, - useRef: mockedUseRef, - } - }) - - try { - const { default: FlowchartFresh } = await import('../index') - const { unmount } = render() - await waitFor(() => { - expect(screen.getByText('test-svg')).toBeInTheDocument() - }, { timeout: 3000 }) - unmount() - } - finally { - vi.doUnmock('react') - } - }) - - it('should tolerate missing hidden container during handDrawn render', async () => { - vi.resetModules() - let pendingContainerRef: unknown | null = null - let patchedContainerRef = false - let containerReadCount = 0 - const virtualContainer = { innerHTML: 'seed' } as HTMLDivElement - - vi.doMock('react', async () => { - const reactActual = await vi.importActual('react') - const mockedUseRef = ((initialValue: unknown) => { - const ref = reactActual.useRef(initialValue as never) - if (!patchedContainerRef && initialValue === null) - pendingContainerRef = ref - - if (!patchedContainerRef - && pendingContainerRef - && typeof initialValue === 'string' - && initialValue.startsWith('mermaid-chart-')) { - Object.defineProperty(pendingContainerRef as { current: unknown }, 'current', { - configurable: true, - get() { - containerReadCount += 1 - if (containerReadCount === 1) - return virtualContainer - return null - }, - set(_value: HTMLDivElement | null) { }, - }) - patchedContainerRef = true - pendingContainerRef = null - } - return ref - }) as typeof reactActual.useRef - - return { - ...reactActual, - useRef: mockedUseRef, - } - }) + it('should cancel a pending classic render on unmount', async () => { + const { default: FlowchartFresh } = await import('../index') vi.useFakeTimers() try { - const { default: FlowchartFresh } = await import('../index') - const { rerender } = render() + const { unmount } = render() + await act(async () => { - fireEvent.click(screen.getByText(HAND_DRAWN_RE)) - rerender() + unmount() await vi.advanceTimersByTimeAsync(350) }) - await Promise.resolve() - expect(screen.getByText('test-svg-api')).toBeInTheDocument() + + expect(vi.mocked(mermaidFresh.render)).not.toHaveBeenCalled() + } + finally { + vi.useRealTimers() + } + }) + + it('should cancel a pending handDrawn render on unmount', async () => { + const { default: FlowchartFresh } = await import('../index') + const { unmount } = render() + + await waitFor(() => { + expect(screen.getByText('test-svg')).toBeInTheDocument() + }, { timeout: 3000 }) + + const initialHandDrawnCalls = vi.mocked(mermaidFresh.mermaidAPI.render).mock.calls.length + + vi.useFakeTimers() + try { + await act(async () => { + fireEvent.click(screen.getByText(HAND_DRAWN_RE)) + }) + + await act(async () => { + unmount() + await vi.advanceTimersByTimeAsync(350) + }) + + expect(vi.mocked(mermaidFresh.mermaidAPI.render).mock.calls.length).toBe(initialHandDrawnCalls) } finally { vi.useRealTimers() - vi.doUnmock('react') } }) }) diff --git a/web/app/components/base/segmented-control/index.tsx b/web/app/components/base/segmented-control/index.tsx index 1f6bd4073f..b5ee1e85bc 100644 --- a/web/app/components/base/segmented-control/index.tsx +++ b/web/app/components/base/segmented-control/index.tsx @@ -4,7 +4,6 @@ import { cva } from 'class-variance-authority' import * as React from 'react' import { cn } from '@/utils/classnames' import Divider from '../divider' -import './index.css' type SegmentedControlOption = { value: T @@ -131,7 +130,7 @@ export const SegmentedControl = ({
{text} {!!(count && size === 'large') && ( -
+
{count}
)} diff --git a/web/app/styles/globals.css b/web/app/styles/globals.css index f99371d180..30d5c14525 100644 --- a/web/app/styles/globals.css +++ b/web/app/styles/globals.css @@ -11,6 +11,7 @@ @import "../components/base/button/index.css"; @import "../components/base/modal/index.css"; @import "../components/base/premium-badge/index.css"; +@import "../components/base/segmented-control/index.css"; @tailwind base; @tailwind components; diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 82fe7c89b4..abe626a576 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -2657,11 +2657,6 @@ "count": 1 } }, - "app/components/base/segmented-control/index.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 1 - } - }, "app/components/base/select/custom.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 2 diff --git a/web/knip.config.ts b/web/knip.config.ts index 5da78bb750..fffb057b41 100644 --- a/web/knip.config.ts +++ b/web/knip.config.ts @@ -7,9 +7,10 @@ const config: KnipConfig = { entry: [ 'scripts/**/*.{js,ts,mjs}', 'bin/**/*.{js,ts,mjs}', + 'taze.config.js', + 'tsslint.config.ts', ], ignore: [ - 'i18n/**', 'public/**', ], ignoreBinaries: [ @@ -19,9 +20,6 @@ const config: KnipConfig = { '@iconify-json/*', '@storybook/addon-onboarding', - - '@tsslint/compat-eslint', - '@tsslint/config', ], rules: { files: 'warn', diff --git a/web/package.json b/web/package.json index 117f63b608..40659c1085 100644 --- a/web/package.json +++ b/web/package.json @@ -3,7 +3,7 @@ "type": "module", "version": "1.13.0", "private": true, - "packageManager": "pnpm@10.32.0", + "packageManager": "pnpm@10.32.1", "imports": { "#i18n": { "react-server": "./i18n-config/lib.server.ts", @@ -32,6 +32,7 @@ "build:vinext": "vinext build", "dev": "next dev", "dev:inspect": "next dev --inspect", + "dev:proxy": "tsx ./scripts/dev-hono-proxy.ts", "dev:vinext": "vinext dev", "gen-doc-paths": "tsx ./scripts/gen-doc-paths.ts", "gen-icons": "node ./scripts/gen-icons.mjs && eslint --fix app/components/base/icons/src/", @@ -50,7 +51,6 @@ "storybook": "storybook dev -p 6006", "storybook:build": "storybook build", "test": "vitest run", - "test:ci": "vitest run --coverage --silent=passed-only", "test:coverage": "vitest run --coverage", "test:watch": "vitest --watch", "type-check": "tsc --noEmit", @@ -58,14 +58,15 @@ "uglify-embed": "node ./bin/uglify-embed" }, "dependencies": { - "@amplitude/analytics-browser": "2.36.3", - "@amplitude/plugin-session-replay-browser": "1.25.21", - "@base-ui/react": "1.2.0", + "@amplitude/analytics-browser": "2.36.4", + "@amplitude/plugin-session-replay-browser": "1.26.1", + "@base-ui/react": "1.3.0", "@emoji-mart/data": "1.2.1", "@floating-ui/react": "0.27.19", "@formatjs/intl-localematcher": "0.8.1", "@headlessui/react": "2.2.9", "@heroicons/react": "2.2.0", + "@hono/node-server": "1.19.11", "@lexical/code": "0.41.0", "@lexical/link": "0.41.0", "@lexical/list": "0.41.0", @@ -85,7 +86,7 @@ "@svgdotjs/svg.js": "3.2.5", "@t3-oss/env-nextjs": "0.13.10", "@tailwindcss/typography": "0.5.19", - "@tanstack/react-form": "1.28.4", + "@tanstack/react-form": "1.28.5", "@tanstack/react-query": "5.90.21", "abcjs": "6.6.2", "ahooks": "3.9.6", @@ -94,9 +95,9 @@ "cmdk": "1.1.1", "copy-to-clipboard": "3.3.3", "cron-parser": "5.5.0", - "dayjs": "1.11.19", + "dayjs": "1.11.20", "decimal.js": "10.6.0", - "dompurify": "3.3.2", + "dompurify": "3.3.3", "echarts": "6.0.0", "echarts-for-react": "3.0.6", "elkjs": "0.11.1", @@ -106,9 +107,10 @@ "es-toolkit": "1.45.1", "fast-deep-equal": "3.1.3", "foxact": "0.2.54", + "hono": "4.12.7", "html-entities": "2.6.0", "html-to-image": "1.11.13", - "i18next": "25.8.17", + "i18next": "25.8.18", "i18next-resources-to-backend": "1.2.1", "immer": "11.1.4", "jotai": "2.18.1", @@ -136,7 +138,7 @@ "react-dom": "19.2.4", "react-easy-crop": "5.5.6", "react-hotkeys-hook": "5.2.4", - "react-i18next": "16.5.6", + "react-i18next": "16.5.8", "react-multi-email": "1.0.25", "react-papaparse": "4.4.0", "react-pdf-highlighter": "8.0.0-rc.0", @@ -164,7 +166,7 @@ "zustand": "5.0.11" }, "devDependencies": { - "@antfu/eslint-config": "7.7.0", + "@antfu/eslint-config": "7.7.2", "@chromatic-com/storybook": "5.0.1", "@egoist/tailwindcss-icons": "1.9.2", "@eslint-react/eslint-plugin": "2.13.0", @@ -183,8 +185,8 @@ "@storybook/nextjs-vite": "10.2.17", "@storybook/react": "10.2.17", "@tanstack/eslint-plugin-query": "5.91.4", - "@tanstack/react-devtools": "0.9.10", - "@tanstack/react-form-devtools": "0.2.17", + "@tanstack/react-devtools": "0.9.13", + "@tanstack/react-form-devtools": "0.2.18", "@tanstack/react-query-devtools": "5.91.3", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "6.9.1", @@ -196,7 +198,7 @@ "@types/js-cookie": "3.0.6", "@types/js-yaml": "4.0.9", "@types/negotiator": "0.6.4", - "@types/node": "25.4.0", + "@types/node": "25.5.0", "@types/postcss-js": "4.1.0", "@types/qs": "6.15.0", "@types/react": "19.2.14", @@ -207,10 +209,10 @@ "@types/semver": "7.7.1", "@types/sortablejs": "1.15.9", "@typescript-eslint/parser": "8.57.0", - "@typescript/native-preview": "7.0.0-dev.20260310.1", - "@vitejs/plugin-react": "5.1.4", + "@typescript/native-preview": "7.0.0-dev.20260312.1", + "@vitejs/plugin-react": "6.0.0", "@vitejs/plugin-rsc": "0.5.21", - "@vitest/coverage-v8": "4.0.18", + "@vitest/coverage-v8": "4.1.0", "agentation": "2.3.2", "autoprefixer": "10.4.27", "code-inspector-plugin": "1.4.4", @@ -231,17 +233,17 @@ "postcss": "8.5.8", "postcss-js": "5.1.0", "react-server-dom-webpack": "19.2.4", - "sass": "1.97.3", + "sass": "1.98.0", "storybook": "10.2.17", "tailwindcss": "3.4.19", + "taze": "19.10.0", "tsx": "4.21.0", "typescript": "5.9.3", "uglify-js": "3.19.3", - "vinext": "0.0.29", - "vite": "8.0.0-beta.18", + "vinext": "https://pkg.pr.new/vinext@18fe3ea", + "vite": "8.0.0", "vite-plugin-inspect": "11.3.3", - "vite-tsconfig-paths": "6.1.1", - "vitest": "4.0.18", + "vitest": "4.1.0", "vitest-canvas-mock": "1.1.3" }, "pnpm": { diff --git a/web/plugins/dev-proxy/cookies.ts b/web/plugins/dev-proxy/cookies.ts new file mode 100644 index 0000000000..a744493892 --- /dev/null +++ b/web/plugins/dev-proxy/cookies.ts @@ -0,0 +1,98 @@ +const DEFAULT_PROXY_TARGET = 'https://cloud.dify.ai' + +const SECURE_COOKIE_PREFIX_PATTERN = /^__(Host|Secure)-/ +const SAME_SITE_NONE_PATTERN = /^samesite=none$/i +const COOKIE_PATH_PATTERN = /^path=/i +const COOKIE_DOMAIN_PATTERN = /^domain=/i +const COOKIE_SECURE_PATTERN = /^secure$/i +const COOKIE_PARTITIONED_PATTERN = /^partitioned$/i + +const HOST_PREFIX_COOKIE_NAMES = new Set([ + 'access_token', + 'csrf_token', + 'refresh_token', + 'webapp_access_token', +]) + +const isPassportCookie = (cookieName: string) => cookieName.startsWith('passport-') + +const shouldUseHostPrefix = (cookieName: string) => { + const normalizedCookieName = cookieName.replace(SECURE_COOKIE_PREFIX_PATTERN, '') + return HOST_PREFIX_COOKIE_NAMES.has(normalizedCookieName) || isPassportCookie(normalizedCookieName) +} + +const toUpstreamCookieName = (cookieName: string) => { + if (cookieName.startsWith('__Host-')) + return cookieName + + if (cookieName.startsWith('__Secure-')) + return `__Host-${cookieName.replace(SECURE_COOKIE_PREFIX_PATTERN, '')}` + + if (!shouldUseHostPrefix(cookieName)) + return cookieName + + return `__Host-${cookieName}` +} + +const toLocalCookieName = (cookieName: string) => cookieName.replace(SECURE_COOKIE_PREFIX_PATTERN, '') + +export const rewriteCookieHeaderForUpstream = (cookieHeader?: string) => { + if (!cookieHeader) + return cookieHeader + + return cookieHeader + .split(/;\s*/) + .filter(Boolean) + .map((cookie) => { + const separatorIndex = cookie.indexOf('=') + if (separatorIndex === -1) + return cookie + + const cookieName = cookie.slice(0, separatorIndex).trim() + const cookieValue = cookie.slice(separatorIndex + 1) + return `${toUpstreamCookieName(cookieName)}=${cookieValue}` + }) + .join('; ') +} + +const rewriteSetCookieValueForLocal = (setCookieValue: string) => { + const [rawCookiePair, ...rawAttributes] = setCookieValue.split(';') + const separatorIndex = rawCookiePair.indexOf('=') + + if (separatorIndex === -1) + return setCookieValue + + const cookieName = rawCookiePair.slice(0, separatorIndex).trim() + const cookieValue = rawCookiePair.slice(separatorIndex + 1) + const rewrittenAttributes = rawAttributes + .map(attribute => attribute.trim()) + .filter(attribute => + !COOKIE_DOMAIN_PATTERN.test(attribute) + && !COOKIE_SECURE_PATTERN.test(attribute) + && !COOKIE_PARTITIONED_PATTERN.test(attribute), + ) + .map((attribute) => { + if (SAME_SITE_NONE_PATTERN.test(attribute)) + return 'SameSite=Lax' + + if (COOKIE_PATH_PATTERN.test(attribute)) + return 'Path=/' + + return attribute + }) + + return [`${toLocalCookieName(cookieName)}=${cookieValue}`, ...rewrittenAttributes].join('; ') +} + +export const rewriteSetCookieHeadersForLocal = (setCookieHeaders?: string | string[]): string[] | undefined => { + if (!setCookieHeaders) + return undefined + + const normalizedHeaders = Array.isArray(setCookieHeaders) + ? setCookieHeaders + : [setCookieHeaders] + + return normalizedHeaders.map(rewriteSetCookieValueForLocal) +} + +export { DEFAULT_PROXY_TARGET } diff --git a/web/plugins/dev-proxy/server.spec.ts b/web/plugins/dev-proxy/server.spec.ts new file mode 100644 index 0000000000..9c950abae0 --- /dev/null +++ b/web/plugins/dev-proxy/server.spec.ts @@ -0,0 +1,113 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { buildUpstreamUrl, createDevProxyApp, isAllowedDevOrigin, resolveDevProxyTargets } from './server' + +describe('dev proxy server', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Scenario: Hono proxy targets should be read directly from env. + it('should resolve Hono proxy targets from env', () => { + // Arrange + const targets = resolveDevProxyTargets({ + HONO_CONSOLE_API_PROXY_TARGET: 'https://console.example.com', + HONO_PUBLIC_API_PROXY_TARGET: 'https://public.example.com', + }) + + // Assert + expect(targets.consoleApiTarget).toBe('https://console.example.com') + expect(targets.publicApiTarget).toBe('https://public.example.com') + }) + + // Scenario: target paths should not be duplicated when the incoming route already includes them. + it('should preserve prefixed targets when building upstream URLs', () => { + // Act + const url = buildUpstreamUrl('https://api.example.com/console/api', '/console/api/apps', '?page=1') + + // Assert + expect(url.href).toBe('https://api.example.com/console/api/apps?page=1') + }) + + // Scenario: only localhost dev origins should be reflected for credentialed CORS. + it('should only allow local development origins', () => { + // Assert + expect(isAllowedDevOrigin('http://localhost:3000')).toBe(true) + expect(isAllowedDevOrigin('http://127.0.0.1:3000')).toBe(true) + expect(isAllowedDevOrigin('https://example.com')).toBe(false) + }) + + // Scenario: proxy requests should rewrite cookies and surface credentialed CORS headers. + it('should proxy api requests through Hono with local cookie rewriting', async () => { + // Arrange + const fetchImpl = vi.fn().mockResolvedValue(new Response('ok', { + status: 200, + headers: [ + ['content-encoding', 'br'], + ['content-length', '123'], + ['set-cookie', '__Host-access_token=abc; Path=/console/api; Domain=cloud.dify.ai; Secure; SameSite=None'], + ['transfer-encoding', 'chunked'], + ], + })) + const app = createDevProxyApp({ + consoleApiTarget: 'https://cloud.dify.ai', + publicApiTarget: 'https://public.dify.ai', + fetchImpl, + }) + + // Act + const response = await app.request('http://127.0.0.1:5001/console/api/apps?page=1', { + headers: { + Origin: 'http://localhost:3000', + Cookie: 'access_token=abc', + }, + }) + + // Assert + expect(fetchImpl).toHaveBeenCalledTimes(1) + expect(fetchImpl).toHaveBeenCalledWith( + new URL('https://cloud.dify.ai/console/api/apps?page=1'), + expect.objectContaining({ + method: 'GET', + headers: expect.any(Headers), + }), + ) + + const [, requestInit] = fetchImpl.mock.calls[0] + const requestHeaders = requestInit?.headers as Headers + expect(requestHeaders.get('cookie')).toBe('__Host-access_token=abc') + expect(requestHeaders.get('origin')).toBe('https://cloud.dify.ai') + expect(response.headers.get('access-control-allow-origin')).toBe('http://localhost:3000') + expect(response.headers.get('access-control-allow-credentials')).toBe('true') + expect(response.headers.get('content-encoding')).toBeNull() + expect(response.headers.get('content-length')).toBeNull() + expect(response.headers.get('transfer-encoding')).toBeNull() + expect(response.headers.getSetCookie()).toEqual([ + 'access_token=abc; Path=/; SameSite=Lax', + ]) + }) + + // Scenario: preflight requests should advertise allowed headers for credentialed cross-origin calls. + it('should answer CORS preflight requests', async () => { + // Arrange + const app = createDevProxyApp({ + consoleApiTarget: 'https://cloud.dify.ai', + publicApiTarget: 'https://public.dify.ai', + fetchImpl: vi.fn(), + }) + + // Act + const response = await app.request('http://127.0.0.1:5001/api/messages', { + method: 'OPTIONS', + headers: { + 'Origin': 'http://localhost:3000', + 'Access-Control-Request-Headers': 'authorization,content-type,x-csrf-token', + }, + }) + + // Assert + expect(response.status).toBe(204) + expect(response.headers.get('access-control-allow-origin')).toBe('http://localhost:3000') + expect(response.headers.get('access-control-allow-credentials')).toBe('true') + expect(response.headers.get('access-control-allow-headers')).toBe('authorization,content-type,x-csrf-token') + }) +}) diff --git a/web/plugins/dev-proxy/server.ts b/web/plugins/dev-proxy/server.ts new file mode 100644 index 0000000000..3708dd746f --- /dev/null +++ b/web/plugins/dev-proxy/server.ts @@ -0,0 +1,202 @@ +import type { Context, Hono } from 'hono' +import { Hono as HonoApp } from 'hono' +import { DEFAULT_PROXY_TARGET, rewriteCookieHeaderForUpstream, rewriteSetCookieHeadersForLocal } from './cookies' + +type DevProxyEnv = Partial> + +export type DevProxyTargets = { + consoleApiTarget: string + publicApiTarget: string +} + +type DevProxyAppOptions = DevProxyTargets & { + fetchImpl?: typeof globalThis.fetch +} + +const LOCAL_DEV_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']) +const ALLOW_METHODS = 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS' +const DEFAULT_ALLOW_HEADERS = 'Authorization, Content-Type, X-CSRF-Token' +const RESPONSE_HEADERS_TO_DROP = [ + 'connection', + 'content-encoding', + 'content-length', + 'keep-alive', + 'set-cookie', + 'transfer-encoding', +] as const + +const appendHeaderValue = (headers: Headers, name: string, value: string) => { + const currentValue = headers.get(name) + if (!currentValue) { + headers.set(name, value) + return + } + + if (currentValue.split(',').map(item => item.trim()).includes(value)) + return + + headers.set(name, `${currentValue}, ${value}`) +} + +export const isAllowedDevOrigin = (origin?: string | null) => { + if (!origin) + return false + + try { + const url = new URL(origin) + return LOCAL_DEV_HOSTS.has(url.hostname) + } + catch { + return false + } +} + +export const applyCorsHeaders = (headers: Headers, origin?: string | null) => { + if (!isAllowedDevOrigin(origin)) + return + + headers.set('Access-Control-Allow-Origin', origin!) + headers.set('Access-Control-Allow-Credentials', 'true') + appendHeaderValue(headers, 'Vary', 'Origin') +} + +export const buildUpstreamUrl = (target: string, requestPath: string, search = '') => { + const targetUrl = new URL(target) + const normalizedTargetPath = targetUrl.pathname === '/' ? '' : targetUrl.pathname.replace(/\/$/, '') + const normalizedRequestPath = requestPath.startsWith('/') ? requestPath : `/${requestPath}` + const hasTargetPrefix = normalizedTargetPath + && (normalizedRequestPath === normalizedTargetPath || normalizedRequestPath.startsWith(`${normalizedTargetPath}/`)) + + targetUrl.pathname = hasTargetPrefix + ? normalizedRequestPath + : `${normalizedTargetPath}${normalizedRequestPath}` + targetUrl.search = search + + return targetUrl +} + +const createProxyRequestHeaders = (request: Request, targetUrl: URL) => { + const headers = new Headers(request.headers) + headers.delete('host') + + if (headers.has('origin')) + headers.set('origin', targetUrl.origin) + + const rewrittenCookieHeader = rewriteCookieHeaderForUpstream(headers.get('cookie') || undefined) + if (rewrittenCookieHeader) + headers.set('cookie', rewrittenCookieHeader) + + return headers +} + +const createUpstreamResponseHeaders = (response: Response, requestOrigin?: string | null) => { + const headers = new Headers(response.headers) + RESPONSE_HEADERS_TO_DROP.forEach(header => headers.delete(header)) + + const rewrittenSetCookies = rewriteSetCookieHeadersForLocal(response.headers.getSetCookie()) + rewrittenSetCookies?.forEach((cookie) => { + headers.append('set-cookie', cookie) + }) + + applyCorsHeaders(headers, requestOrigin) + return headers +} + +const proxyRequest = async ( + context: Context, + target: string, + fetchImpl: typeof globalThis.fetch, +) => { + const requestUrl = new URL(context.req.url) + const targetUrl = buildUpstreamUrl(target, requestUrl.pathname, requestUrl.search) + const requestHeaders = createProxyRequestHeaders(context.req.raw, targetUrl) + const requestInit: RequestInit & { duplex?: 'half' } = { + method: context.req.method, + headers: requestHeaders, + redirect: 'manual', + } + + if (context.req.method !== 'GET' && context.req.method !== 'HEAD') { + requestInit.body = context.req.raw.body + requestInit.duplex = 'half' + } + + const upstreamResponse = await fetchImpl(targetUrl, requestInit) + const responseHeaders = createUpstreamResponseHeaders(upstreamResponse, context.req.header('origin')) + + return new Response(upstreamResponse.body, { + status: upstreamResponse.status, + statusText: upstreamResponse.statusText, + headers: responseHeaders, + }) +} + +const registerProxyRoute = ( + app: Hono, + path: '/console/api' | '/api', + target: string, + fetchImpl: typeof globalThis.fetch, +) => { + app.all(path, context => proxyRequest(context, target, fetchImpl)) + app.all(`${path}/*`, context => proxyRequest(context, target, fetchImpl)) +} + +export const resolveDevProxyTargets = (env: DevProxyEnv = {}): DevProxyTargets => { + const consoleApiTarget = env.HONO_CONSOLE_API_PROXY_TARGET + || DEFAULT_PROXY_TARGET + const publicApiTarget = env.HONO_PUBLIC_API_PROXY_TARGET + || consoleApiTarget + + return { + consoleApiTarget, + publicApiTarget, + } +} + +export const createDevProxyApp = (options: DevProxyAppOptions) => { + const app = new HonoApp() + const fetchImpl = options.fetchImpl || globalThis.fetch + + app.onError((error, context) => { + console.error('[dev-hono-proxy]', error) + + const headers = new Headers() + applyCorsHeaders(headers, context.req.header('origin')) + + return new Response('Upstream proxy request failed.', { + status: 502, + headers, + }) + }) + + app.use('*', async (context, next) => { + if (context.req.method === 'OPTIONS') { + const headers = new Headers() + applyCorsHeaders(headers, context.req.header('origin')) + headers.set('Access-Control-Allow-Methods', ALLOW_METHODS) + headers.set( + 'Access-Control-Allow-Headers', + context.req.header('Access-Control-Request-Headers') || DEFAULT_ALLOW_HEADERS, + ) + if (context.req.header('Access-Control-Request-Private-Network') === 'true') + headers.set('Access-Control-Allow-Private-Network', 'true') + + return new Response(null, { + status: 204, + headers, + }) + } + + await next() + applyCorsHeaders(context.res.headers, context.req.header('origin')) + }) + + registerProxyRoute(app, '/console/api', options.consoleApiTarget, fetchImpl) + registerProxyRoute(app, '/api', options.publicApiTarget, fetchImpl) + + return app +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index e04c52923c..2231d6c8ad 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -57,14 +57,14 @@ importers: .: dependencies: '@amplitude/analytics-browser': - specifier: 2.36.3 - version: 2.36.3 + specifier: 2.36.4 + version: 2.36.4 '@amplitude/plugin-session-replay-browser': - specifier: 1.25.21 - version: 1.25.21(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0) + specifier: 1.26.1 + version: 1.26.1(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0) '@base-ui/react': - specifier: 1.2.0 - version: 1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 1.3.0 + version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@emoji-mart/data': specifier: 1.2.1 version: 1.2.1 @@ -80,6 +80,9 @@ importers: '@heroicons/react': specifier: 2.2.0 version: 2.2.0(react@19.2.4) + '@hono/node-server': + specifier: 1.19.11 + version: 1.19.11(hono@4.12.7) '@lexical/code': specifier: npm:lexical-code-no-prism@0.41.0 version: lexical-code-no-prism@0.41.0(@lexical/utils@0.41.0)(lexical@0.41.0) @@ -138,8 +141,8 @@ importers: specifier: 0.5.19 version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/react-form': - specifier: 1.28.4 - version: 1.28.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 1.28.5 + version: 1.28.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-query': specifier: 5.90.21 version: 5.90.21(react@19.2.4) @@ -165,14 +168,14 @@ importers: specifier: 5.5.0 version: 5.5.0 dayjs: - specifier: 1.11.19 - version: 1.11.19 + specifier: 1.11.20 + version: 1.11.20 decimal.js: specifier: 10.6.0 version: 10.6.0 dompurify: - specifier: 3.3.2 - version: 3.3.2 + specifier: 3.3.3 + version: 3.3.3 echarts: specifier: 6.0.0 version: 6.0.0 @@ -200,6 +203,9 @@ importers: foxact: specifier: 0.2.54 version: 0.2.54(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + hono: + specifier: 4.12.7 + version: 4.12.7 html-entities: specifier: 2.6.0 version: 2.6.0 @@ -207,8 +213,8 @@ importers: specifier: 1.11.13 version: 1.11.13 i18next: - specifier: 25.8.17 - version: 25.8.17(typescript@5.9.3) + specifier: 25.8.18 + version: 25.8.18(typescript@5.9.3) i18next-resources-to-backend: specifier: 1.2.1 version: 1.2.1 @@ -259,13 +265,13 @@ importers: version: 1.0.0 next: specifier: 16.1.6 - version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3) + version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nuqs: specifier: 2.8.9 - version: 2.8.9(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4) + version: 2.8.9(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4) pinyin-pro: specifier: 3.28.0 version: 3.28.0 @@ -291,8 +297,8 @@ importers: specifier: 5.2.4 version: 5.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-i18next: - specifier: 16.5.6 - version: 16.5.6(i18next@25.8.17(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + specifier: 16.5.8 + version: 16.5.8(i18next@25.8.18(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-multi-email: specifier: 1.0.25 version: 1.0.25(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -370,8 +376,8 @@ importers: version: 5.0.11(@types/react@19.2.14)(immer@11.1.4)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) devDependencies: '@antfu/eslint-config': - specifier: 7.7.0 - version: 7.7.0(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 7.7.2 + version: 7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) '@chromatic-com/storybook': specifier: 5.0.1 version: 5.0.1(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) @@ -407,7 +413,7 @@ importers: version: 4.2.0 '@storybook/addon-docs': specifier: 10.2.17 - version: 10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + version: 10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/addon-links': specifier: 10.2.17 version: 10.2.17(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) @@ -419,7 +425,7 @@ importers: version: 10.2.17(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@storybook/nextjs-vite': specifier: 10.2.17 - version: 10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + version: 10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': specifier: 10.2.17 version: 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) @@ -427,11 +433,11 @@ importers: specifier: 5.91.4 version: 5.91.4(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) '@tanstack/react-devtools': - specifier: 0.9.10 - version: 0.9.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11) + specifier: 0.9.13 + version: 0.9.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11) '@tanstack/react-form-devtools': - specifier: 0.2.17 - version: 0.2.17(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11) + specifier: 0.2.18 + version: 0.2.18(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11) '@tanstack/react-query-devtools': specifier: 5.91.3 version: 5.91.3(@tanstack/react-query@5.90.21(react@19.2.4))(react@19.2.4) @@ -466,8 +472,8 @@ importers: specifier: 0.6.4 version: 0.6.4 '@types/node': - specifier: 25.4.0 - version: 25.4.0 + specifier: 25.5.0 + version: 25.5.0 '@types/postcss-js': specifier: 4.1.0 version: 4.1.0 @@ -499,17 +505,17 @@ importers: specifier: 8.57.0 version: 8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) '@typescript/native-preview': - specifier: 7.0.0-dev.20260310.1 - version: 7.0.0-dev.20260310.1 + specifier: 7.0.0-dev.20260312.1 + version: 7.0.0-dev.20260312.1 '@vitejs/plugin-react': - specifier: 5.1.4 - version: 5.1.4(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 6.0.0 + version: 6.0.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) '@vitejs/plugin-rsc': specifier: 0.5.21 - version: 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/coverage-v8': - specifier: 4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.0 + version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) agentation: specifier: 2.3.2 version: 2.3.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -554,7 +560,7 @@ importers: version: 1.16.0 knip: specifier: 5.86.0 - version: 5.86.0(@types/node@25.4.0)(typescript@5.9.3) + version: 5.86.0(@types/node@25.5.0)(typescript@5.9.3) lint-staged: specifier: 16.3.3 version: 16.3.3 @@ -571,14 +577,17 @@ importers: specifier: 19.2.4 version: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) sass: - specifier: 1.97.3 - version: 1.97.3 + specifier: 1.98.0 + version: 1.98.0 storybook: specifier: 10.2.17 version: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tailwindcss: specifier: 3.4.19 version: 3.4.19(tsx@4.21.0)(yaml@2.8.2) + taze: + specifier: 19.10.0 + version: 19.10.0 tsx: specifier: 4.21.0 version: 4.21.0 @@ -589,23 +598,20 @@ importers: specifier: 3.19.3 version: 3.19.3 vinext: - specifier: 0.0.29 - version: 0.0.29(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: https://pkg.pr.new/vinext@18fe3ea + version: https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) vite: - specifier: 8.0.0-beta.18 - version: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: 8.0.0 + version: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-inspect: specifier: 11.3.3 - version: 11.3.3(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - vite-tsconfig-paths: - specifier: 6.1.1 - version: 6.1.1(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 11.3.3(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: - specifier: 4.0.18 - version: 4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: 4.1.0 + version: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) vitest-canvas-mock: specifier: 1.1.3 - version: 1.1.3(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 1.1.3(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) packages: @@ -619,17 +625,17 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@amplitude/analytics-browser@2.36.3': - resolution: {integrity: sha512-eGQ5G3yUr4KtcK3gefasuS7uSFj6b/Rj9xqA2reCsQI4ZP/UJphDDuEQ0l6wLWMVZlLvNjD0ybywf5HnVZZ7OQ==} + '@amplitude/analytics-browser@2.36.4': + resolution: {integrity: sha512-R/lhtbBXs3WXJG0GFetKDHD6KO4psoPdBq5x8fJ530TIGasiZxL7s0ZzzKt93sTsgM79vgEkViwteACPMz0J8w==} - '@amplitude/analytics-client-common@2.4.33': - resolution: {integrity: sha512-Y16cZw4i4Ecp0eLDXqkzOCUQk+fk8H4f8NXcilmdX7K3sJ6TpJY9Y9SwkYT34DyW3aO9WZNNmlHUcwo04Wt0mA==} + '@amplitude/analytics-client-common@2.4.34': + resolution: {integrity: sha512-WfNerOcClGam6TTzjGsAj/NO2y2nhCVCIsaKJkO36MdQoE+Qv2NGNrCCl4qWZn10XTUv9T+sbMngzh3xQNU+mQ==} '@amplitude/analytics-connector@1.6.4': resolution: {integrity: sha512-SpIv0IQMNIq6SH3UqFGiaZyGSc7PBZwRdq7lvP0pBxW8i4Ny+8zwI0pV+VMfMHQwWY3wdIbWw5WQphNjpdq1/Q==} - '@amplitude/analytics-core@2.41.3': - resolution: {integrity: sha512-UujceP+22zVi/0hl4ccrKEQ94rsCTOVtNZIeM2UxELYLZj24fqjUgdTX+x+gUuzgzOSDRiohkRLwRt6hiBmgmw==} + '@amplitude/analytics-core@2.41.4': + resolution: {integrity: sha512-sZi4fD6KSOUTvyMtBtCX8E/1VjUrS1VIShyJpKWoYKW55IF5y1/knVkhLzbycNKzMCOv/pl/vXHeXDqHy5eU9w==} '@amplitude/analytics-types@2.11.1': resolution: {integrity: sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A==} @@ -637,23 +643,23 @@ packages: '@amplitude/experiment-core@0.7.2': resolution: {integrity: sha512-Wc2NWvgQ+bLJLeF0A9wBSPIaw0XuqqgkPKsoNFQrmS7r5Djd56um75In05tqmVntPJZRvGKU46pAp8o5tdf4mA==} - '@amplitude/plugin-autocapture-browser@1.23.3': - resolution: {integrity: sha512-h+dMoXqmKSSlkk4sdPGwIl0I+/6trG8tJvYifpIKWexooxe5z9GcC33pFdYebvF6odupU7lk152fEOlkq/OmIw==} + '@amplitude/plugin-autocapture-browser@1.23.4': + resolution: {integrity: sha512-yBRiZU21PrHpfKa26nDBtBkak1LxM4kp/S0ffMaDDB/Mo7sXAWCmSJjs8C+nUuKdIUpmkIl1EQvPQhC15ZV9YQ==} - '@amplitude/plugin-network-capture-browser@1.9.3': - resolution: {integrity: sha512-DvDHUVqD7I+dsnyaj+BzKkcyvf3kWfuWZ3UTBHOxzLFwj2wkG684EJ96AjO35AK26R61z0hROaNjbBRYbInfyA==} + '@amplitude/plugin-network-capture-browser@1.9.4': + resolution: {integrity: sha512-jbIehDontSaiisAJRm/aVp8xV083JaqfVAlO1vJz/hpmctyfHFbnrTF2yEVaZ90/w7RQC/D7yJuedaSXFCjJBQ==} - '@amplitude/plugin-page-url-enrichment-browser@0.6.7': - resolution: {integrity: sha512-0YxL+fIyERqnkLF4QrVOmzAbe0NWLi48zyPrJ6rUu9muR/oAe5jpcLvgRgLE68W427+FmbiN5O0HyE7lbC5YqA==} + '@amplitude/plugin-page-url-enrichment-browser@0.6.8': + resolution: {integrity: sha512-q465BkvPw5NC573eDuIu+Mu4UiPr7CVmMP0NqBlavyzUkP2Nnj4ckpeEpod1CdH+jxUvqUz1w79EPkT+vpSDlQ==} - '@amplitude/plugin-page-view-tracking-browser@2.8.3': - resolution: {integrity: sha512-iGEEqnsIHUAe+5gpeEB9j9n0KK2n+GPdkJMJWNAUluURq31jME35zSharK6PVaNfmkKTIEOLbWoIq/kj6abZMQ==} + '@amplitude/plugin-page-view-tracking-browser@2.8.4': + resolution: {integrity: sha512-lxqMHxZe6Wn82kOh1Fo1ihTKX+OkAsCzDLuzq5+x6JvEyLzZcNJDU4tQDlyr4XuPXE+pRSrHkg1aTUGkkU2zlA==} - '@amplitude/plugin-session-replay-browser@1.25.21': - resolution: {integrity: sha512-WqWZsV/YWOg69YE+D0mSHjgz+obNGySuDClkFq07P6p9gNJFPLLIShObWSpZFQx6MZRaYXQmIFWZ+9bAh3WCmA==} + '@amplitude/plugin-session-replay-browser@1.26.1': + resolution: {integrity: sha512-RolriLHbTXLHRFNTGImz5vj2DiFG5SeZ90KnM2qf7pkHlfKhwNoLuQ0vsKbXLQuW0Kx0Oc4DVQSRWtMNUkNweA==} - '@amplitude/plugin-web-vitals-browser@1.1.18': - resolution: {integrity: sha512-VCjCeVGjvqM7L50LSWGEKP0zWZOPVAD/rs1gZYd8ZO0XPJh6ZQvsroHawB3/KAVpCJmVdb0721yAMJcqtP/yyw==} + '@amplitude/plugin-web-vitals-browser@1.1.19': + resolution: {integrity: sha512-c37a+BxvEuwVrEI2e0643pjkFA+ZdAqU+39Gy8u+ERrbtXubpgyxqFI2+WzGRVuOiV1eFbjQlMwiJoETF/TDzQ==} '@amplitude/rrdom@2.0.0-alpha.35': resolution: {integrity: sha512-W9ImCKtgFB8oBKd7td0TH7JKkQ/3iwu5bfLXcOvzxLj7+RSD1k1gfDyncooyobwBV8j4FMiTyj2N53tJ6rFgaw==} @@ -681,14 +687,14 @@ packages: '@amplitude/rrweb@2.0.0-alpha.35': resolution: {integrity: sha512-qFaZDNMkjolZUVv1OxrWngGl38FH0iF0jtybd/vhuOzvwohJjyKL9Tgoulj8osj21/4BUpGEhWweGeJygjoJJw==} - '@amplitude/session-replay-browser@1.31.7': - resolution: {integrity: sha512-A7yD1Q8waDqkcmkQ06yt8m7zOs6H8s6XjJr+rvgmEPVApBbBiBRlc83ceyvb3Hf4cNGsthA2Uc2V9I9Db9C/BQ==} + '@amplitude/session-replay-browser@1.32.1': + resolution: {integrity: sha512-m9TVPQF6tuCg+YWwfwUjH9szTiAesALdTdw4qG6V4pAh+GtqTWB409lSjyp9Hhqea/2egT7P9rsimiO6grk3eA==} '@amplitude/targeting@0.2.0': resolution: {integrity: sha512-/50ywTrC4hfcfJVBbh5DFbqMPPfaIOivZeb5Gb+OGM03QrA+lsUqdvtnKLNuWtceD4H6QQ2KFzPJ5aAJLyzVDA==} - '@antfu/eslint-config@7.7.0': - resolution: {integrity: sha512-lkxb84o8z4v1+me51XlrHHF6zvOZfvTu6Y11t6h6v17JSMl9yoNHwC0Sqp/NfMTHie/LGgjyXOupXpQCXxfs1Q==} + '@antfu/eslint-config@7.7.2': + resolution: {integrity: sha512-CGCcwASry9hk8cOYl82sfTsqmma4DyDfmQSaOJ9p8OtJlVMouDV0MCueDZzUu7iHl+DraSSN6J6sXSkzG6LzcQ==} hasBin: true peerDependencies: '@angular-eslint/eslint-plugin': ^21.1.0 @@ -754,6 +760,11 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@antfu/ni@28.3.0': + resolution: {integrity: sha512-JbRijiCNAGcQcyPfV0EXOJYwV27e/srXfTvETqzbbh4jzHBV2pDYiBz8rj5SyzX27aTbCK+qXR3x6g2WKokcrA==} + engines: {node: '>=20'} + hasBin: true + '@antfu/utils@8.1.1': resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} @@ -854,8 +865,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.2.0': - resolution: {integrity: sha512-O6aEQHcm+QyGTFY28xuwRD3SEJGZOBDpyjN2WvpfWYFVhg+3zfXPysAILqtM0C1kWC82MccOE/v1j+GHXE4qIw==} + '@base-ui/react@1.3.0': + resolution: {integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA==} engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17 || ^18 || ^19 @@ -865,8 +876,8 @@ packages: '@types/react': optional: true - '@base-ui/utils@0.2.5': - resolution: {integrity: sha512-oYC7w0gp76RI5MxprlGLV0wze0SErZaRl3AAkeP3OnNB/UBMb6RqNf6ZSIlxOc9Qp68Ab3C2VOcJQyRs7Xc7Vw==} + '@base-ui/utils@0.2.6': + resolution: {integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw==} peerDependencies: '@types/react': ^17 || ^18 || ^19 react: ^17 || ^18 || ^19 @@ -1341,11 +1352,20 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc + '@henrygd/queue@1.2.0': + resolution: {integrity: sha512-jW/BLSTpcvExDhqJGxtIPgGr2O0IFF8XUNDwEbfCfhrXT8a4xztQ9Lv6U/vbYzYC0xVWn+3zv6YnLUh3bEFUKA==} + '@heroicons/react@2.2.0': resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} peerDependencies: react: '>= 16 || ^19.0.0-rc' + '@hono/node-server@1.19.11': + resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2055,6 +2075,9 @@ packages: '@preact/signals-core@1.14.0': resolution: {integrity: sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==} + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} @@ -2334,97 +2357,97 @@ packages: resolution: {integrity: sha512-UuBOt7BOsKVOkFXRe4Ypd/lADuNIfqJXv8GvHqtXaTYXPPKkj2nS2zPllVsrtRjcomDhIJVBnZwfmlI222WH8g==} engines: {node: '>=14.0.0'} - '@rolldown/binding-android-arm64@1.0.0-rc.8': - resolution: {integrity: sha512-5bcmMQDWEfWUq3m79Mcf/kbO6e5Jr6YjKSsA1RnpXR6k73hQ9z1B17+4h93jXpzHvS18p7bQHM1HN/fSd+9zog==} + '@rolldown/binding-android-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.8': - resolution: {integrity: sha512-dcHPd5N4g9w2iiPRJmAvO0fsIWzF2JPr9oSuTjxLL56qu+oML5aMbBMNwWbk58Mt3pc7vYs9CCScwLxdXPdRsg==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.8': - resolution: {integrity: sha512-mw0VzDvoj8AuR761QwpdCFN0sc/jspuc7eRYJetpLWd+XyansUrH3C7IgNw6swBOgQT9zBHNKsVCjzpfGJlhUA==} + '@rolldown/binding-darwin-x64@1.0.0-rc.9': + resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.8': - resolution: {integrity: sha512-xNrRa6mQ9NmMIJBdJtPMPG8Mso0OhM526pDzc/EKnRrIrrkHD1E0Z6tONZRmUeJElfsQ6h44lQQCcDilSNIvSQ==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': + resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.8': - resolution: {integrity: sha512-WgCKoO6O/rRUwimWfEJDeztwJJmuuX0N2bYLLRxmXDTtCwjToTOqk7Pashl/QpQn3H/jHjx0b5yCMbcTVYVpNg==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': + resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.8': - resolution: {integrity: sha512-tOHgTOQa8G4Z3ULj4G3NYOGGJEsqPHR91dT72u63OtVsZ7B6wFJKOx+ZKv+pvwzxWz92/I2ycaqi2/Ll4l+rlg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.8': - resolution: {integrity: sha512-oRbxcgDujCi2Yp1GTxoUFsIFlZsuPHU4OV4AzNc3/6aUmR4lfm9FK0uwQu82PJsuUwnF2jFdop3Ep5c1uK7Uxg==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': + resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.8': - resolution: {integrity: sha512-oaLRyUHw8kQE5M89RqrDJZ10GdmGJcMeCo8tvaE4ukOofqgjV84AbqBSH6tTPjeT2BHv+xlKj678GBuIb47lKA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.8': - resolution: {integrity: sha512-1hjSKFrod5MwBBdLOOA0zpUuSfSDkYIY+QqcMcIU1WOtswZtZdUkcFcZza9b2HcAb0bnpmmyo0LZcaxLb2ov1g==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.8': - resolution: {integrity: sha512-a1+F0aV4Wy9tT3o+cHl3XhOy6aFV+B8Ll+/JFj98oGkb6lGk3BNgrxd+80RwYRVd23oLGvj3LwluKYzlv1PEuw==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.8': - resolution: {integrity: sha512-bGyXCFU11seFrf7z8PcHSwGEiFVkZ9vs+auLacVOQrVsI8PFHJzzJROF3P6b0ODDmXr0m6Tj5FlDhcXVk0Jp8w==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': + resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.8': - resolution: {integrity: sha512-n8d+L2bKgf9G3+AM0bhHFWdlz9vYKNim39ujRTieukdRek0RAo2TfG2uEnV9spa4r4oHUfL9IjcY3M9SlqN1gw==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.8': - resolution: {integrity: sha512-4R4iJDIk7BrJdteAbEAICXPoA7vZoY/M0OBfcRlQxzQvUYMcEp2GbC/C8UOgQJhu2TjGTpX1H8vVO1xHWcRqQA==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': + resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.8': - resolution: {integrity: sha512-3lwnklba9qQOpFnQ7EW+A1m4bZTWXZE4jtehsZ0YOl2ivW1FQqp5gY7X2DLuKITggesyuLwcmqS11fA7NtrmrA==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': + resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.8': - resolution: {integrity: sha512-VGjCx9Ha1P/r3tXGDZyG0Fcq7Q0Afnk64aaKzr1m40vbn1FL8R3W0V1ELDvPgzLXaaqK/9PnsqSaLWXfn6JtGQ==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': + resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2435,8 +2458,11 @@ packages: '@rolldown/pluginutils@1.0.0-rc.5': resolution: {integrity: sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw==} - '@rolldown/pluginutils@1.0.0-rc.8': - resolution: {integrity: sha512-wzJwL82/arVfeSP3BLr1oTy40XddjtEdrdgtJ4lLRBu06mP3q/8HGM6K0JRlQuTA3XB0pNJx2so/nmpY4xyOew==} + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + + '@rolldown/pluginutils@1.0.0-rc.9': + resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} '@rollup/plugin-replace@6.0.3': resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} @@ -2836,12 +2862,6 @@ packages: resolution: {integrity: sha512-GRxmPw4OHZ2oZeIEUkEwt/NDvuEqzEYRAjzUVMs+I0pd4C7k1ySOiuJK2CqF+K/yEAR3YZNkW3ExrpDarh9Vwg==} engines: {node: '>=18'} - '@tanstack/devtools-ui@0.4.4': - resolution: {integrity: sha512-5xHXFyX3nom0UaNfiOM92o6ziaHjGo3mcSGe2HD5Xs8dWRZNpdZ0Smd0B9ddEhy0oB+gXyMzZgUJb9DmrZV0Mg==} - engines: {node: '>=18'} - peerDependencies: - solid-js: 1.9.11 - '@tanstack/devtools-ui@0.5.0': resolution: {integrity: sha512-nNZ14054n31fWB61jtWhZYLRdQ3yceCE3G/RINoINUB0RqIGZAIm9DnEDwOTAOfqt4/a/D8vNk8pJu6RQUp74g==} engines: {node: '>=18'} @@ -2869,9 +2889,10 @@ packages: vue: optional: true - '@tanstack/devtools@0.10.11': - resolution: {integrity: sha512-Nk1rHsv6S/5Krzz+uL5jldW9gKb3s6rkkVl1L9oVYHNClKthbrk2hGef4Di6yj449QIOqVExTdDujjQ4roq1dg==} + '@tanstack/devtools@0.10.14': + resolution: {integrity: sha512-bg1e0PyjmMMsc9VSOGb9etu15CpFdAwlQ5DD2xS6N93iTPgCPWXiZQFZygrEDoKnnx1x7BM6QTaiukizaejgSA==} engines: {node: '>=18'} + hasBin: true peerDependencies: solid-js: 1.9.11 @@ -2884,11 +2905,11 @@ packages: typescript: optional: true - '@tanstack/form-core@1.28.4': - resolution: {integrity: sha512-2eox5ePrJ6kvA1DXD5QHk/GeGr3VFZ0uYR63UgQOe7bUg6h1JfXaIMqTjZK9sdGyE4oRNqFpoW54H0pZM7nObQ==} + '@tanstack/form-core@1.28.5': + resolution: {integrity: sha512-8lYnduHHfP6uaXF9+2OLnh3Fo27tH4TdtekWLG2b/Bp26ynbrWG6L4qhBgEb7VcvTpJw/RjvJF/JyFhZkG3pfQ==} - '@tanstack/form-devtools@0.2.17': - resolution: {integrity: sha512-1i+hAmhbyOm4lJOoQWvDA41bHFFyeSjA79kHxirU2FCSGWk58u1+eyvw6+dUweWfJLW2yTFU9VyQBbFSbG0qig==} + '@tanstack/form-devtools@0.2.18': + resolution: {integrity: sha512-KZC4isGvFek3XdfBwlIRRqdJEN7uzfrTuu3ZgBIMwnmSmsMl+EuxeWDM4/g6neEANn9c5uOD6AsN09kecXo39g==} peerDependencies: solid-js: 1.9.11 @@ -2902,8 +2923,8 @@ packages: '@tanstack/query-devtools@5.93.0': resolution: {integrity: sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==} - '@tanstack/react-devtools@0.9.10': - resolution: {integrity: sha512-WKFU8SXN7DLM7EyD2aUAhmk7JGNeONWhQozAH2qDCeOjyc3Yzxs4BxeoyKMYyEiX/eCp8ZkMTf/pJX6vm2LGeA==} + '@tanstack/react-devtools@0.9.13': + resolution: {integrity: sha512-O9YXTEe2dlnw2pPNKFZ4Wk7zC4qrDvc0SAALKfMVedeZ2Dyd0LEJUabYS6GPm+DmnrBhc7nJx6Zqc9aDjFrj4g==} engines: {node: '>=18'} peerDependencies: '@types/react': '>=16.8' @@ -2911,13 +2932,13 @@ packages: react: '>=16.8' react-dom: '>=16.8' - '@tanstack/react-form-devtools@0.2.17': - resolution: {integrity: sha512-0asnrx9xBRuHptFh6hOB6sl1PrPb4gmjxHU/25L+lnNc0+OLgP13t3+CpC8qS95mdg2HJ42wieG1SvZTsuj0Nw==} + '@tanstack/react-form-devtools@0.2.18': + resolution: {integrity: sha512-/nE6gQeBb+D2vPEFO/6LNrk7sUI2ps80RE+zteLFiOH/5kUrhwc8QuaVZ6PyVal43eF1rpXayaOVnCJv+5G/+Q==} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-form@1.28.4': - resolution: {integrity: sha512-ZGBwl9JM2u0kol7jAWpqAkr2JSHfXJaLPsFDZWPf+ewpVkwngTTW/rGgtoDe5uVpHoDIpOhzpPCAh6O1SjGEOg==} + '@tanstack/react-form@1.28.5': + resolution: {integrity: sha512-CL8IeWkeXnEEDsHt5wBuIOZvSYrKiLRtsC9ca0LzfJJ22SYCma9cBmh1UX1EBX0o3gH2U21PmUf+y5f9OJNoEQ==} peerDependencies: '@tanstack/react-start': '*' react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3187,8 +3208,8 @@ packages: '@types/negotiator@0.6.4': resolution: {integrity: sha512-elf6BsTq+AkyNsb2h5cGNst2Mc7dPliVoAPm1fXglC/BM3f2pFA40BaSSv3E5lyHteEawVKLP+8TwiY1DMNb3A==} - '@types/node@25.4.0': - resolution: {integrity: sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==} + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} '@types/papaparse@5.5.2': resolution: {integrity: sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==} @@ -3305,43 +3326,43 @@ packages: resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-mzMXa3AIcEh3OKkuVYf/bvb8D6wsJ/kn1KLwBGb4C1NCak102spy1Bio1myrTMyjb/R+hRM7gom+KZ/5IHJvIg==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-AhPdPuVe4osxWoeImS21jVhc0VJ2QnzLUZtEFMakY0Rf70C0b6il/m7hwRf9wkr9xXZLVOVJ1kYrpvQRuHFE0Q==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-mAhe3pzvMP2Zfjny7deJWa2ro6DLGPli4UtcKPeX1XbJc9fVbR/YtgtoHJTxZ6KJTqIzpaZGrOxMuJiVdMTU6A==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-9I0P1/c/mQ6UVcQq7SYY/FJD23IN5T2y4GbSFOKQvzNVASV0tMnX4YV8YNf6b5jcwCzrVcrGNKKgWCj8xEFf8Q==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-tpDGCiZ10AsHxfEAxHn5SDXPSSmM9m75IMrieri/ljGOqkV/WeqjCj+OmLdjXTxv1FbLKbSXitxGxDeSmqR2zg==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-xwoMywagcvx9F2ocM+ybeg7eH9PHDpx1FBGOrloL1/xkGC4BCrn/RcaAe0AhzXzoJfHHmg7Sz9VzYmTR4N1Kqw==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-0xAS7aYeZPBVRU8+5vmaZVCCqUf82YE3KF/9G8x8OFoCuMPCZYUouzOFf/Y+kCrwyZguoPrd7TFiFdScVCj2gA==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-/nAOhSLTxMJfHY+2cKdUxi2wYadf3g1GtC3VzgPfZMNxA28dJ8x75T26aSLaFYluh7cCSAwuGesCImijQDS2Lw==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-J+Jwg/bWEwCrMIXAv2omBQYDt99PXbG4lFlDC9wKSnA0RXPyMeuRlFHc9HK0dbuB1teyZb7no23Q/EJcb5dpeQ==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-vZs0LLpZw50Ac0TCmF9ND7KphJBhOfp9fxLhC+hFWaUU1iCQRjv1MtvroitF5OJKb21qFPJxkU+kfhlCRxLfqg==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-4SeUJxIQqERZXIT0ZsPpN3w2mDzDu17kVtOFhaHWxiPfa6RBAy7+uSpBqIHutaOR/q0gMnZWkFWXioTN8RIzOg==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-4LY/gd9cj1xDY2nEthB7WDW4j/fIYJ9wp9H71nOLd0wNNtkfqRXWSkQEeb+RByhV+dIb/n6kWbQQMeNfk7q4VQ==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-+9cCjzRzPhVfRtxx5713aDkjzAI/Ffq4KRf0eqM3l2mSCIvA9uk4kaeYM0c6QDe2Jcz6inFtkCy+3gLL84tIoQ==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-EP2JPo9s9EPUwXSX83qTImlDHhgkLeBbJ2MMdj+XrfBltHAvHKktzeSS73UhP77s/TnTkJR6BTWHENKKvLRbGQ==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260310.1': - resolution: {integrity: sha512-g1Hi6V5YTYLzh4cf9yJwUMLmnFvqhqe/5V/rZ6Lx70P4BwSW5QuQBcXQMPWSD9CpsiO/xbhGfmtHZaxClAzosw==} + '@typescript/native-preview@7.0.0-dev.20260312.1': + resolution: {integrity: sha512-FwhlXG/yG0d7b2UmooBYyszLMpICRYdYGE6v65ZlMnH7cWKQyyFpMFgH9suRf3Np4QCbN+7qisj+F23kQOidVw==} hasBin: true '@ungap/structured-clone@1.3.0': @@ -3378,6 +3399,19 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitejs/plugin-react@6.0.0': + resolution: {integrity: sha512-Bu5/eP6td3WI654+tRq+ryW1PbgA90y5pqMKpb3U7UpNk6VjI53P/ncPUd192U9dSrepLy7DHnq1XEMDz5H++w==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + '@vitejs/plugin-rsc@0.5.21': resolution: {integrity: sha512-uNayLT8IKvWoznvQyfwKuGiEFV28o7lxUDnw/Av36VCuGpDFZnMmvVCwR37gTvnSmnpul9V0tdJqY3tBKEaDqw==} peerDependencies: @@ -3389,11 +3423,11 @@ packages: react-server-dom-webpack: optional: true - '@vitest/coverage-v8@4.0.18': - resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==} + '@vitest/coverage-v8@4.1.0': + resolution: {integrity: sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==} peerDependencies: - '@vitest/browser': 4.0.18 - vitest: 4.0.18 + '@vitest/browser': 4.1.0 + vitest: 4.1.0 peerDependenciesMeta: '@vitest/browser': optional: true @@ -3414,14 +3448,14 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@vitest/expect@4.1.0': + resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + '@vitest/mocker@4.1.0': + resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 peerDependenciesMeta: msw: optional: true @@ -3431,26 +3465,26 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + '@vitest/pretty-format@4.1.0': + resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@vitest/runner@4.1.0': + resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/snapshot@4.1.0': + resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/spy@4.1.0': + resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@vitest/utils@4.1.0': + resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -3660,8 +3694,8 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} - ast-v8-to-istanbul@0.3.12: - resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} @@ -4220,8 +4254,8 @@ packages: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - dayjs@1.11.19: - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -4265,6 +4299,9 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} @@ -4272,6 +4309,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -4312,9 +4352,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.3.2: - resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==} - engines: {node: '>=20'} + dompurify@3.3.3: + resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -4907,6 +4946,9 @@ packages: functional-red-black-tree@1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + fzf@0.5.2: + resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -5045,6 +5087,10 @@ packages: highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + hono@4.12.7: + resolution: {integrity: sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==} + engines: {node: '>=16.9.0'} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -5086,8 +5132,8 @@ packages: i18next-resources-to-backend@1.2.1: resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} - i18next@25.8.17: - resolution: {integrity: sha512-vWtCttyn5bpOK4hWbRAe1ZXkA+Yzcn2OcACT+WJavtfGMcxzkfvXTLMeOU8MUhRmAySKjU4VVuKlo0sSGeBokA==} + i18next@25.8.18: + resolution: {integrity: sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -5950,6 +5996,9 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-releases@2.0.36: resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} @@ -5998,6 +6047,9 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -6282,6 +6334,9 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -6344,8 +6399,8 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - react-i18next@16.5.6: - resolution: {integrity: sha512-Ua7V2/efA88ido7KyK51fb8Ki8M/sRfW8LR/rZ/9ZKr2luhuTI7kwYZN5agT1rWG7aYm5G0RYE/6JR8KJoCMDw==} + react-i18next@16.5.8: + resolution: {integrity: sha512-2ABeHHlakxVY+LSirD+OiERxFL6+zip0PaHo979bgwzeHg27Sqc82xxXWIrSFmfWX0ZkrvXMHwhsi/NGUf5VQg==} peerDependencies: i18next: '>= 25.6.2' react: '>= 16.8.0' @@ -6609,8 +6664,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rolldown@1.0.0-rc.8: - resolution: {integrity: sha512-RGOL7mz/aoQpy/y+/XS9iePBfeNRDUdozrhCEJxdpJyimW8v6yp4c30q6OviUU5AnUJVLRL9GP//HUs6N3ALrQ==} + rolldown@1.0.0-rc.9: + resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -6641,8 +6696,8 @@ packages: safe-json-stringify@1.2.0: resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==} - sass@1.97.3: - resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==} + sass@1.98.0: + resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==} engines: {node: '>=14.0.0'} hasBin: true @@ -6786,8 +6841,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} storybook@10.2.17: resolution: {integrity: sha512-yueTpl5YJqLzQqs3CanxNdAAfFU23iP0j+JVJURE4ghfEtRmWfWoZWLGkVcyjmgum7UmjwAlqRuOjQDNvH89kw==} @@ -6946,6 +7001,10 @@ packages: resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==} engines: {node: '>=18'} + taze@19.10.0: + resolution: {integrity: sha512-pylMr+Yl8m4ZXu5LwWdtfCOJhLW69NuoeZTLtRzTekfheQ1ix5wOWjQlTb8S3SSxLlDcYFuajQOWllO5iyE0jg==} + hasBin: true + terser-webpack-plugin@5.4.0: resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} engines: {node: '>= 10.13.0'} @@ -7133,6 +7192,12 @@ packages: resolution: {integrity: sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w==} engines: {node: '>=14'} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + + unconfig@7.5.0: + resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==} + undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} @@ -7286,8 +7351,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vinext@0.0.29: - resolution: {integrity: sha512-T7kzhgX2TbM+unIHaehiz5H7D2i0TLWhUUqvdnu5PgiBzCSMiH8G2rzE7dA8Tc1oEhywuzYP+pt5bfXkk9lk+w==} + vinext@https://pkg.pr.new/vinext@18fe3ea: + resolution: {tarball: https://pkg.pr.new/vinext@18fe3ea} + version: 0.0.5 engines: {node: '>=22'} hasBin: true peerDependencies: @@ -7296,7 +7362,7 @@ packages: react: '>=19.2.0' react-dom: '>=19.2.0' react-server-dom-webpack: ^19.2.4 - vite: ^7.0.0 || ^8.0.0-beta.0 + vite: ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@mdx-js/rollup': optional: true @@ -7351,48 +7417,8 @@ packages: peerDependencies: vite: '*' - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vite@8.0.0-beta.18: - resolution: {integrity: sha512-azgNbWdsO/WBqHQxwSCy+zd+Fq+37Fix2hn64cQuiUvaaGGSUac7f8RGQhI1aQl9OKbfWblrCFLWs+tln06c2A==} + vite@8.0.0: + resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -7447,20 +7473,21 @@ packages: peerDependencies: vitest: ^3.0.0 || ^4.0.0 - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + vitest@4.1.0: + resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 + '@vitest/browser-playwright': 4.1.0 + '@vitest/browser-preview': 4.1.0 + '@vitest/browser-webdriverio': 4.1.0 + '@vitest/ui': 4.1.0 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -7716,26 +7743,26 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@amplitude/analytics-browser@2.36.3': + '@amplitude/analytics-browser@2.36.4': dependencies: - '@amplitude/analytics-core': 2.41.3 - '@amplitude/plugin-autocapture-browser': 1.23.3 - '@amplitude/plugin-network-capture-browser': 1.9.3 - '@amplitude/plugin-page-url-enrichment-browser': 0.6.7 - '@amplitude/plugin-page-view-tracking-browser': 2.8.3 - '@amplitude/plugin-web-vitals-browser': 1.1.18 + '@amplitude/analytics-core': 2.41.4 + '@amplitude/plugin-autocapture-browser': 1.23.4 + '@amplitude/plugin-network-capture-browser': 1.9.4 + '@amplitude/plugin-page-url-enrichment-browser': 0.6.8 + '@amplitude/plugin-page-view-tracking-browser': 2.8.4 + '@amplitude/plugin-web-vitals-browser': 1.1.19 tslib: 2.8.1 - '@amplitude/analytics-client-common@2.4.33': + '@amplitude/analytics-client-common@2.4.34': dependencies: '@amplitude/analytics-connector': 1.6.4 - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-core': 2.41.4 '@amplitude/analytics-types': 2.11.1 tslib: 2.8.1 '@amplitude/analytics-connector@1.6.4': {} - '@amplitude/analytics-core@2.41.3': + '@amplitude/analytics-core@2.41.4': dependencies: '@amplitude/analytics-connector': 1.6.4 '@types/zen-observable': 0.8.3 @@ -7749,43 +7776,43 @@ snapshots: dependencies: js-base64: 3.7.8 - '@amplitude/plugin-autocapture-browser@1.23.3': + '@amplitude/plugin-autocapture-browser@1.23.4': dependencies: - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-core': 2.41.4 tslib: 2.8.1 - '@amplitude/plugin-network-capture-browser@1.9.3': + '@amplitude/plugin-network-capture-browser@1.9.4': dependencies: - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-core': 2.41.4 tslib: 2.8.1 - '@amplitude/plugin-page-url-enrichment-browser@0.6.7': + '@amplitude/plugin-page-url-enrichment-browser@0.6.8': dependencies: - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-core': 2.41.4 tslib: 2.8.1 - '@amplitude/plugin-page-view-tracking-browser@2.8.3': + '@amplitude/plugin-page-view-tracking-browser@2.8.4': dependencies: - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-core': 2.41.4 tslib: 2.8.1 - '@amplitude/plugin-session-replay-browser@1.25.21(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0)': + '@amplitude/plugin-session-replay-browser@1.26.1(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0)': dependencies: - '@amplitude/analytics-client-common': 2.4.33 - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-client-common': 2.4.34 + '@amplitude/analytics-core': 2.41.4 '@amplitude/analytics-types': 2.11.1 '@amplitude/rrweb-plugin-console-record': 2.0.0-alpha.35(@amplitude/rrweb@2.0.0-alpha.35) '@amplitude/rrweb-record': 2.0.0-alpha.35 - '@amplitude/session-replay-browser': 1.31.7(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0) + '@amplitude/session-replay-browser': 1.32.1(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0) idb-keyval: 6.2.2 tslib: 2.8.1 transitivePeerDependencies: - '@amplitude/rrweb' - rollup - '@amplitude/plugin-web-vitals-browser@1.1.18': + '@amplitude/plugin-web-vitals-browser@1.1.19': dependencies: - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-core': 2.41.4 tslib: 2.8.1 web-vitals: 5.1.0 @@ -7826,10 +7853,10 @@ snapshots: base64-arraybuffer: 1.0.2 mitt: 3.0.1 - '@amplitude/session-replay-browser@1.31.7(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0)': + '@amplitude/session-replay-browser@1.32.1(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0)': dependencies: - '@amplitude/analytics-client-common': 2.4.33 - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-client-common': 2.4.34 + '@amplitude/analytics-core': 2.41.4 '@amplitude/analytics-types': 2.11.1 '@amplitude/experiment-core': 0.7.2 '@amplitude/rrweb-packer': 2.0.0-alpha.35 @@ -7847,14 +7874,14 @@ snapshots: '@amplitude/targeting@0.2.0': dependencies: - '@amplitude/analytics-client-common': 2.4.33 - '@amplitude/analytics-core': 2.41.3 + '@amplitude/analytics-client-common': 2.4.34 + '@amplitude/analytics-core': 2.41.4 '@amplitude/analytics-types': 2.11.1 '@amplitude/experiment-core': 0.7.2 idb: 8.0.0 tslib: 2.8.1 - '@antfu/eslint-config@7.7.0(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@antfu/eslint-config@7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 1.1.0 @@ -7864,7 +7891,7 @@ snapshots: '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.3(jiti@1.21.7)) '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/parser': 8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/eslint-plugin': 1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) ansis: 4.2.0 cac: 7.0.0 eslint: 10.0.3(jiti@1.21.7) @@ -7914,6 +7941,14 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 + '@antfu/ni@28.3.0': + dependencies: + ansis: 4.2.0 + fzf: 0.5.2 + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + '@antfu/utils@8.1.1': {} '@asamuzakjp/css-color@5.0.1': @@ -8048,10 +8083,10 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@base-ui/react@1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@base-ui/react@1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@babel/runtime': 7.28.6 - '@base-ui/utils': 0.2.5(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@base-ui/utils': 0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@floating-ui/utils': 0.2.11 react: 19.2.4 @@ -8061,7 +8096,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@base-ui/utils@0.2.5(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@base-ui/utils@0.2.6(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@babel/runtime': 7.28.6 '@floating-ui/utils': 0.2.11 @@ -8554,10 +8589,16 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) + '@henrygd/queue@1.2.0': {} + '@heroicons/react@2.2.0(react@19.2.4)': dependencies: react: 19.2.4 + '@hono/node-server@1.19.11(hono@4.12.7)': + dependencies: + hono: 4.12.7 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -8712,11 +8753,11 @@ snapshots: dependencies: minipass: 7.1.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) optionalDependencies: typescript: 5.9.3 @@ -9277,6 +9318,10 @@ snapshots: '@preact/signals-core@1.14.0': {} + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + '@radix-ui/primitive@1.1.3': {} '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)': @@ -9571,58 +9616,60 @@ snapshots: '@rgrove/parse-xml@4.2.0': {} - '@rolldown/binding-android-arm64@1.0.0-rc.8': + '@rolldown/binding-android-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.8': + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.8': + '@rolldown/binding-darwin-x64@1.0.0-rc.9': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.8': + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.8': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.8': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.8': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.8': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.8': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.8': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.8': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.8': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.8': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.8': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.8': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': optional: true '@rolldown/pluginutils@1.0.0-rc.3': {} '@rolldown/pluginutils@1.0.0-rc.5': {} - '@rolldown/pluginutils@1.0.0-rc.8': {} + '@rolldown/pluginutils@1.0.0-rc.7': {} + + '@rolldown/pluginutils@1.0.0-rc.9': {} '@rollup/plugin-replace@6.0.3(rollup@4.59.0)': dependencies: @@ -9793,10 +9840,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/addon-docs@10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) - '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@storybook/react-dom-shim': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) react: 19.2.4 @@ -9826,25 +9873,25 @@ snapshots: storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/builder-vite@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/csf-plugin@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 rollup: 4.59.0 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) webpack: 5.105.4(esbuild@0.27.2)(uglify-js@3.19.3) '@storybook/global@5.0.0': {} @@ -9854,18 +9901,18 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@storybook/nextjs-vite@10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/nextjs-vite@10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@storybook/react-vite': 10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3) + '@storybook/react-vite': 10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-plugin-storybook-nextjs: 3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-plugin-storybook-nextjs: 3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -9882,11 +9929,11 @@ snapshots: react-dom: 19.2.4(react@19.2.4) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/react-vite@10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/react-vite@10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) - '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -9896,7 +9943,7 @@ snapshots: resolve: 1.22.11 storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tsconfig-paths: 4.2.0 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup @@ -9978,18 +10025,10 @@ snapshots: '@tanstack/devtools-event-client@0.4.1': {} - '@tanstack/devtools-ui@0.4.4(csstype@3.2.3)(solid-js@1.9.11)': - dependencies: - clsx: 2.1.1 - goober: 2.1.18(csstype@3.2.3) - solid-js: 1.9.11 - transitivePeerDependencies: - - csstype - '@tanstack/devtools-ui@0.5.0(csstype@3.2.3)(solid-js@1.9.11)': dependencies: clsx: 2.1.1 - dayjs: 1.11.19 + dayjs: 1.11.20 goober: 2.1.18(csstype@3.2.3) solid-js: 1.9.11 transitivePeerDependencies: @@ -10005,7 +10044,7 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools@0.10.11(csstype@3.2.3)(solid-js@1.9.11)': + '@tanstack/devtools@0.10.14(csstype@3.2.3)(solid-js@1.9.11)': dependencies: '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.11) '@solid-primitives/keyboard': 1.3.5(solid-js@1.9.11) @@ -10030,19 +10069,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/form-core@1.28.4': + '@tanstack/form-core@1.28.5': dependencies: '@tanstack/devtools-event-client': 0.4.1 '@tanstack/pacer-lite': 0.1.1 '@tanstack/store': 0.9.2 - '@tanstack/form-devtools@0.2.17(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11)': + '@tanstack/form-devtools@0.2.18(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11)': dependencies: - '@tanstack/devtools-ui': 0.4.4(csstype@3.2.3)(solid-js@1.9.11) + '@tanstack/devtools-ui': 0.5.0(csstype@3.2.3)(solid-js@1.9.11) '@tanstack/devtools-utils': 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11) - '@tanstack/form-core': 1.28.4 + '@tanstack/form-core': 1.28.5 clsx: 2.1.1 - dayjs: 1.11.19 + dayjs: 1.11.20 goober: 2.1.18(csstype@3.2.3) solid-js: 1.9.11 transitivePeerDependencies: @@ -10058,9 +10097,9 @@ snapshots: '@tanstack/query-devtools@5.93.0': {} - '@tanstack/react-devtools@0.9.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)': + '@tanstack/react-devtools@0.9.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)': dependencies: - '@tanstack/devtools': 0.10.11(csstype@3.2.3)(solid-js@1.9.11) + '@tanstack/devtools': 0.10.14(csstype@3.2.3)(solid-js@1.9.11) '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) react: 19.2.4 @@ -10071,10 +10110,10 @@ snapshots: - solid-js - utf-8-validate - '@tanstack/react-form-devtools@0.2.17(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11)': + '@tanstack/react-form-devtools@0.2.18(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11)': dependencies: '@tanstack/devtools-utils': 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11) - '@tanstack/form-devtools': 0.2.17(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11) + '@tanstack/form-devtools': 0.2.18(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11) react: 19.2.4 transitivePeerDependencies: - '@types/react' @@ -10083,9 +10122,9 @@ snapshots: - solid-js - vue - '@tanstack/react-form@1.28.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-form@1.28.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/form-core': 1.28.4 + '@tanstack/form-core': 1.28.5 '@tanstack/react-store': 0.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 transitivePeerDependencies: @@ -10400,13 +10439,13 @@ snapshots: '@types/negotiator@0.6.4': {} - '@types/node@25.4.0': + '@types/node@25.5.0': dependencies: undici-types: 7.18.2 '@types/papaparse@5.5.2': dependencies: - '@types/node': 25.4.0 + '@types/node': 25.5.0 '@types/postcss-js@4.1.0': dependencies: @@ -10449,7 +10488,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 25.4.0 + '@types/node': 25.5.0 optional: true '@types/zen-observable@0.8.3': {} @@ -10571,36 +10610,36 @@ snapshots: '@typescript-eslint/types': 8.57.0 eslint-visitor-keys: 5.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260310.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260312.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260310.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260312.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260310.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260312.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260310.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260312.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260310.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260312.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260310.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260312.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260310.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260312.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260310.1': + '@typescript/native-preview@7.0.0-dev.20260312.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260310.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260310.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260310.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260310.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260310.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260310.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260310.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260312.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260312.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260312.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260312.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260312.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260312.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260312.1 '@ungap/structured-clone@1.3.0': {} @@ -10608,13 +10647,13 @@ snapshots: dependencies: unpic: 4.2.2 - '@unpic/react@1.0.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@unpic/react@1.0.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@unpic/core': 1.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3) + next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) '@upsetjs/venn.js@2.0.0': optionalDependencies: @@ -10630,7 +10669,7 @@ snapshots: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vitejs/plugin-react@5.1.4(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@5.1.4(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -10638,11 +10677,16 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + + '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -10654,33 +10698,33 @@ snapshots: srvx: 0.11.9 strip-literal: 3.1.0 turbo-stream: 3.2.0 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vitefu: 1.1.2(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) optionalDependencies: react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.18 - ast-v8-to-istanbul: 0.3.12 + '@vitest/utils': 4.1.0 + ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 magicast: 0.5.2 obug: 2.1.1 - std-env: 3.10.0 + std-env: 4.0.0 tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/eslint-plugin@1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/eslint-plugin@1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: '@typescript-eslint/scope-manager': 8.57.0 '@typescript-eslint/utils': 8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) eslint: 10.0.3(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 - vitest: 4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -10692,39 +10736,40 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.0.18': + '@vitest/expect@4.1.0': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.18 + '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.4.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.0.18': + '@vitest/pretty-format@4.1.0': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.18': + '@vitest/runner@4.1.0': dependencies: - '@vitest/utils': 4.0.18 + '@vitest/utils': 4.1.0 pathe: 2.0.3 - '@vitest/snapshot@4.0.18': + '@vitest/snapshot@4.1.0': dependencies: - '@vitest/pretty-format': 4.0.18 + '@vitest/pretty-format': 4.1.0 + '@vitest/utils': 4.1.0 magic-string: 0.30.21 pathe: 2.0.3 @@ -10732,7 +10777,7 @@ snapshots: dependencies: tinyspy: 4.0.4 - '@vitest/spy@4.0.18': {} + '@vitest/spy@4.1.0': {} '@vitest/utils@3.2.4': dependencies: @@ -10740,9 +10785,10 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - '@vitest/utils@4.0.18': + '@vitest/utils@4.1.0': dependencies: - '@vitest/pretty-format': 4.0.18 + '@vitest/pretty-format': 4.1.0 + convert-source-map: 2.0.0 tinyrainbow: 3.0.3 '@volar/language-core@2.4.28': @@ -10900,7 +10946,7 @@ snapshots: dependencies: '@babel/runtime': 7.28.6 '@types/js-cookie': 3.0.6 - dayjs: 1.11.19 + dayjs: 1.11.20 intersection-observer: 0.12.2 js-cookie: 3.0.5 lodash: 4.17.23 @@ -10981,7 +11027,7 @@ snapshots: dependencies: tslib: 2.8.1 - ast-v8-to-istanbul@0.3.12: + ast-v8-to-istanbul@1.0.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -11558,7 +11604,7 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - dayjs@1.11.19: {} + dayjs@1.11.20: {} debug@4.4.3: dependencies: @@ -11591,12 +11637,16 @@ snapshots: define-lazy-prop@3.0.0: {} + defu@6.1.4: {} + delaunator@5.0.1: dependencies: robust-predicates: 3.0.2 dequal@2.0.3: {} + destr@2.0.5: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -11631,7 +11681,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.3.2: + dompurify@3.3.3: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -12428,6 +12478,8 @@ snapshots: functional-red-black-tree@1.0.1: {} + fzf@0.5.2: {} + gensync@1.0.0-beta.2: {} get-east-asian-width@1.5.0: {} @@ -12638,6 +12690,8 @@ snapshots: highlightjs-vue@1.0.0: {} + hono@4.12.7: {} + html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.15.0 @@ -12685,7 +12739,7 @@ snapshots: dependencies: '@babel/runtime': 7.28.6 - i18next@25.8.17(typescript@5.9.3): + i18next@25.8.18(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.6 optionalDependencies: @@ -12836,7 +12890,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.4.0 + '@types/node': 25.5.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -12947,10 +13001,10 @@ snapshots: khroma@2.1.0: {} - knip@5.86.0(@types/node@25.4.0)(typescript@5.9.3): + knip@5.86.0(@types/node@25.5.0)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 25.4.0 + '@types/node': 25.5.0 fast-glob: 3.3.3 formatly: 0.3.0 jiti: 2.6.1 @@ -13385,8 +13439,8 @@ snapshots: d3: 7.9.0 d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 - dayjs: 1.11.19 - dompurify: 3.3.2 + dayjs: 1.11.20 + dompurify: 3.3.3 katex: 0.16.38 khroma: 2.1.0 lodash-es: 4.17.23 @@ -13775,7 +13829,7 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3): + next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0): dependencies: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 @@ -13794,7 +13848,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.1.6 '@next/swc-win32-arm64-msvc': 16.1.6 '@next/swc-win32-x64-msvc': 16.1.6 - sass: 1.97.3 + sass: 1.98.0 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -13814,6 +13868,8 @@ snapshots: node-addon-api@7.1.1: optional: true + node-fetch-native@1.6.7: {} + node-releases@2.0.36: {} normalize-path@3.0.0: {} @@ -13824,12 +13880,12 @@ snapshots: dependencies: boolbase: 1.0.0 - nuqs@2.8.9(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4): + nuqs@2.8.9(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.4 optionalDependencies: - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3) + next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) object-assign@4.1.1: {} @@ -13839,6 +13895,12 @@ snapshots: obug@2.1.1: {} + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + ohash@2.0.11: {} once@1.4.0: @@ -14156,6 +14218,8 @@ snapshots: quansync@0.2.11: {} + quansync@1.0.0: {} + queue-microtask@1.2.3: {} radash@12.1.1: {} @@ -14227,11 +14291,11 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-i18next@16.5.6(i18next@25.8.17(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + react-i18next@16.5.8(i18next@25.8.18(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.6 html-parse-stringify: 3.0.1 - i18next: 25.8.17(typescript@5.9.3) + i18next: 25.8.18(typescript@5.9.3) react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: @@ -14571,26 +14635,26 @@ snapshots: robust-predicates@3.0.2: {} - rolldown@1.0.0-rc.8: + rolldown@1.0.0-rc.9: dependencies: '@oxc-project/types': 0.115.0 - '@rolldown/pluginutils': 1.0.0-rc.8 + '@rolldown/pluginutils': 1.0.0-rc.9 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.8 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.8 - '@rolldown/binding-darwin-x64': 1.0.0-rc.8 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.8 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.8 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.8 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.8 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.8 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.8 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.8 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.8 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.8 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.8 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.8 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.8 + '@rolldown/binding-android-arm64': 1.0.0-rc.9 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.9 + '@rolldown/binding-darwin-x64': 1.0.0-rc.9 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.9 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.9 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.9 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.9 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.9 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.9 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 rollup@4.59.0: dependencies: @@ -14645,7 +14709,7 @@ snapshots: safe-json-stringify@1.2.0: {} - sass@1.97.3: + sass@1.98.0: dependencies: chokidar: 4.0.3 immutable: 5.1.5 @@ -14811,7 +14875,7 @@ snapshots: stackback@0.0.2: {} - std-env@3.10.0: {} + std-env@4.0.0: {} storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: @@ -15020,6 +15084,22 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + taze@19.10.0: + dependencies: + '@antfu/ni': 28.3.0 + '@henrygd/queue': 1.2.0 + cac: 7.0.0 + find-up-simple: 1.0.1 + ofetch: 1.5.1 + package-manager-detector: 1.6.0 + pathe: 2.0.3 + pnpm-workspace-yaml: 1.6.0 + restore-cursor: 5.1.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + unconfig: 7.5.0 + yaml: 2.8.2 + terser-webpack-plugin@5.4.0(esbuild@0.27.2)(uglify-js@3.19.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -15172,6 +15252,19 @@ snapshots: unbash@2.2.0: {} + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + + unconfig@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + defu: 6.1.4 + jiti: 2.6.1 + quansync: 1.0.0 + unconfig-core: 7.5.0 + undici-types@7.18.2: {} undici@7.22.0: {} @@ -15326,36 +15419,36 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@0.0.29(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vinext@https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: - '@unpic/react': 1.0.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@unpic/react': 1.0.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vercel/og': 0.8.6 - '@vitejs/plugin-react': 5.1.4(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-react': 5.1.4(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) magic-string: 0.30.21 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rsc-html-stream: 0.0.7 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-commonjs: 0.10.4 - vite-tsconfig-paths: 6.1.1(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite-tsconfig-paths: 6.1.1(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) optionalDependencies: '@mdx-js/rollup': 3.1.1(rollup@4.59.0) - '@vitejs/plugin-rsc': 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-rsc': 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) transitivePeerDependencies: - next - supports-color - typescript - vite-dev-rpc@1.1.0(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-dev-rpc@1.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: birpc: 2.9.0 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-hot-client: 2.1.0(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-hot-client: 2.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - vite-hot-client@2.1.0(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-hot-client@2.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-commonjs@0.10.4: dependencies: @@ -15370,7 +15463,7 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 - vite-plugin-inspect@11.3.3(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-inspect@11.3.3(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: ansis: 4.2.0 debug: 4.4.3 @@ -15380,130 +15473,102 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-dev-rpc: 1.1.0(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-dev-rpc: 1.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - vite-plugin-storybook-nextjs@3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-storybook-nextjs@3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@next/env': 16.0.0 image-size: 2.0.2 magic-string: 0.30.21 module-alias: 2.3.4 - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3) + next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.8 - rollup: 4.59.0 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.4.0 - fsevents: 2.3.3 - jiti: 1.21.7 - lightningcss: 1.32.0 - sass: 1.97.3 - terser: 5.46.0 - tsx: 4.21.0 - yaml: 2.8.2 - - vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@oxc-project/runtime': 0.115.0 lightningcss: 1.32.0 picomatch: 4.0.3 postcss: 8.5.8 - rolldown: 1.0.0-rc.8 + rolldown: 1.0.0-rc.9 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.4.0 + '@types/node': 25.5.0 esbuild: 0.27.2 fsevents: 2.3.3 jiti: 1.21.7 - sass: 1.97.3 + sass: 1.98.0 terser: 5.46.0 tsx: 4.21.0 yaml: 2.8.2 - vitefu@1.1.2(vite@8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vitefu@1.1.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: - vite: 8.0.0-beta.18(@types/node@25.4.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vitest-canvas-mock@1.1.3(vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vitest-canvas-mock@1.1.3(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: 4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - vitest@4.0.18(@types/node@25.4.0)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 + '@vitest/expect': 4.1.0 + '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.1.0 + '@vitest/runner': 4.1.0 + '@vitest/snapshot': 4.1.0 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 + es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.10.0 + std-env: 4.0.0 tinybench: 2.9.0 tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.4.0)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.4.0 + '@types/node': 25.5.0 jsdom: 28.1.0(canvas@3.2.1) transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml void-elements@3.1.0: {} diff --git a/web/scripts/dev-hono-proxy.ts b/web/scripts/dev-hono-proxy.ts new file mode 100644 index 0000000000..f550d47ae8 --- /dev/null +++ b/web/scripts/dev-hono-proxy.ts @@ -0,0 +1,21 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { serve } from '@hono/node-server' +import { loadEnv } from 'vite' +import { createDevProxyApp, resolveDevProxyTargets } from '../plugins/dev-proxy/server' + +const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const mode = process.env.MODE || process.env.NODE_ENV || 'development' +const env = loadEnv(mode, projectRoot, '') + +const host = env.HONO_PROXY_HOST || '127.0.0.1' +const port = Number(env.HONO_PROXY_PORT || 5001) +const app = createDevProxyApp(resolveDevProxyTargets(env)) + +serve({ + fetch: app.fetch, + hostname: host, + port, +}) + +console.log(`[dev-hono-proxy] listening on http://${host}:${port}`) diff --git a/web/taze.config.js b/web/taze.config.js new file mode 100644 index 0000000000..7ffd76f94e --- /dev/null +++ b/web/taze.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'taze' + +export default defineConfig({ + exclude: [ + // We are going to replace these + 'react-syntax-highlighter', + 'react-window', + '@types/react-window', + + // We can not upgrade these yet + 'tailwind-merge', + 'tailwindcss', + ], + + write: true, + install: false, + recursive: true, + interactive: true, +}) diff --git a/web/vite.config.ts b/web/vite.config.ts index a5ebd9d212..d0c7e947a2 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,10 +1,11 @@ +/// + import path from 'node:path' import { fileURLToPath } from 'node:url' import react from '@vitejs/plugin-react' import vinext from 'vinext' import { defineConfig } from 'vite' import Inspect from 'vite-plugin-inspect' -import tsconfigPaths from 'vite-tsconfig-paths' import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr' @@ -20,8 +21,6 @@ export default defineConfig(({ mode }) => { return { plugins: isTest ? [ - // TODO: remove tsconfigPaths from test config after vitest supports it natively - tsconfigPaths(), react(), { // Stub .mdx files so components importing them can be unit-tested @@ -46,7 +45,8 @@ export default defineConfig(({ mode }) => { injectTarget: browserInitializerInjectTarget, projectRoot, }), - vinext(), + react(), + vinext({ react: false }), customI18nHmrPlugin({ injectTarget: browserInitializerInjectTarget }), // reactGrabOpenFilePlugin({ // injectTarget: browserInitializerInjectTarget, @@ -78,6 +78,7 @@ export default defineConfig(({ mode }) => { environment: 'jsdom', globals: true, setupFiles: ['./vitest.setup.ts'], + reporters: ['agent'], coverage: { provider: 'v8', reporter: isCI ? ['json', 'json-summary'] : ['text', 'json', 'json-summary'], From 1104d35bbbcc30bd876d3688fd72c0cfab1277f7 Mon Sep 17 00:00:00 2001 From: lif <1835304752@qq.com> Date: Fri, 13 Mar 2026 15:06:50 +0800 Subject: [PATCH 02/37] chore: remove unused WEAVIATE_GRPC_ENABLED config option (#33378) Signed-off-by: majiayu000 <1835304752@qq.com> --- api/.env.example | 1 - api/configs/middleware/vdb/weaviate_config.py | 5 ----- api/tests/integration_tests/.env.example | 1 - dev/pytest/pytest_config_tests.py | 2 -- 4 files changed, 9 deletions(-) diff --git a/api/.env.example b/api/.env.example index ab8b6c5287..8fbe2e4643 100644 --- a/api/.env.example +++ b/api/.env.example @@ -188,7 +188,6 @@ VECTOR_INDEX_NAME_PREFIX=Vector_index # Weaviate configuration WEAVIATE_ENDPOINT=http://localhost:8080 WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih -WEAVIATE_GRPC_ENABLED=false WEAVIATE_BATCH_SIZE=100 WEAVIATE_TOKENIZATION=word diff --git a/api/configs/middleware/vdb/weaviate_config.py b/api/configs/middleware/vdb/weaviate_config.py index 6f4fccaa7f..2d1216c0d1 100644 --- a/api/configs/middleware/vdb/weaviate_config.py +++ b/api/configs/middleware/vdb/weaviate_config.py @@ -17,11 +17,6 @@ class WeaviateConfig(BaseSettings): default=None, ) - WEAVIATE_GRPC_ENABLED: bool = Field( - description="Whether to enable gRPC for Weaviate connection (True for gRPC, False for HTTP)", - default=True, - ) - WEAVIATE_GRPC_ENDPOINT: str | None = Field( description="URL of the Weaviate gRPC server (e.g., 'grpc://localhost:50051' or 'grpcs://weaviate.example.com:443')", default=None, diff --git a/api/tests/integration_tests/.env.example b/api/tests/integration_tests/.env.example index 39effbab58..37f8830482 100644 --- a/api/tests/integration_tests/.env.example +++ b/api/tests/integration_tests/.env.example @@ -60,7 +60,6 @@ VECTOR_STORE=weaviate # Weaviate configuration WEAVIATE_ENDPOINT=http://localhost:8080 WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih -WEAVIATE_GRPC_ENABLED=false WEAVIATE_BATCH_SIZE=100 WEAVIATE_TOKENIZATION=word diff --git a/dev/pytest/pytest_config_tests.py b/dev/pytest/pytest_config_tests.py index 1ec95deb09..1ae115f85c 100644 --- a/dev/pytest/pytest_config_tests.py +++ b/dev/pytest/pytest_config_tests.py @@ -38,7 +38,6 @@ BASE_API_AND_DOCKER_CONFIG_SET_DIFF = { "UPSTASH_VECTOR_URL", "USING_UGC_INDEX", "WEAVIATE_BATCH_SIZE", - "WEAVIATE_GRPC_ENABLED", } BASE_API_AND_DOCKER_COMPOSE_CONFIG_SET_DIFF = { @@ -86,7 +85,6 @@ BASE_API_AND_DOCKER_COMPOSE_CONFIG_SET_DIFF = { "VIKINGDB_CONNECTION_TIMEOUT", "VIKINGDB_SOCKET_TIMEOUT", "WEAVIATE_BATCH_SIZE", - "WEAVIATE_GRPC_ENABLED", } API_CONFIG_SET = set(dotenv_values(Path("api") / Path(".env.example")).keys()) From fe561ef3d010a0ce5a65992a8538e9ce36e839dc Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:11:24 +0800 Subject: [PATCH 03/37] feat(workflow): add edge context menu with delete support (#33391) --- .../__tests__/workflow-edge-events.spec.tsx | 396 ++++++++++++++++++ .../workflow/edge-contextmenu.spec.tsx | 340 +++++++++++++++ .../components/workflow/edge-contextmenu.tsx | 62 +++ .../__tests__/use-edges-interactions.spec.ts | 83 +++- .../__tests__/use-panel-interactions.spec.ts | 21 +- .../use-selection-interactions.spec.ts | 11 +- .../workflow/hooks/use-edges-interactions.ts | 143 +++++-- .../workflow/hooks/use-nodes-interactions.ts | 7 + .../workflow/hooks/use-panel-interactions.ts | 10 + .../hooks/use-selection-interactions.ts | 3 + web/app/components/workflow/index.tsx | 5 + .../components/workflow/node-contextmenu.tsx | 7 +- .../components/workflow/panel-contextmenu.tsx | 8 +- .../store/__tests__/workflow-store.spec.ts | 1 + .../workflow/store/workflow/panel-slice.ts | 8 + 15 files changed, 1051 insertions(+), 54 deletions(-) create mode 100644 web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx create mode 100644 web/app/components/workflow/edge-contextmenu.spec.tsx create mode 100644 web/app/components/workflow/edge-contextmenu.tsx diff --git a/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx new file mode 100644 index 0000000000..1e40ea65da --- /dev/null +++ b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx @@ -0,0 +1,396 @@ +import type { EdgeChange, ReactFlowProps } from 'reactflow' +import type { Edge, Node } from '../types' +import { act, fireEvent, screen } from '@testing-library/react' +import * as React from 'react' +import { FlowType } from '@/types/common' +import { WORKFLOW_DATA_UPDATE } from '../constants' +import { Workflow } from '../index' +import { renderWorkflowComponent } from './workflow-test-env' + +const reactFlowState = vi.hoisted(() => ({ + lastProps: null as ReactFlowProps | null, +})) + +type WorkflowUpdateEvent = { + type: string + payload: { + nodes: Node[] + edges: Edge[] + } +} + +const eventEmitterState = vi.hoisted(() => ({ + subscription: null as null | ((payload: WorkflowUpdateEvent) => void), +})) + +const workflowHookMocks = vi.hoisted(() => ({ + handleNodeDragStart: vi.fn(), + handleNodeDrag: vi.fn(), + handleNodeDragStop: vi.fn(), + handleNodeEnter: vi.fn(), + handleNodeLeave: vi.fn(), + handleNodeClick: vi.fn(), + handleNodeConnect: vi.fn(), + handleNodeConnectStart: vi.fn(), + handleNodeConnectEnd: vi.fn(), + handleNodeContextMenu: vi.fn(), + handleHistoryBack: vi.fn(), + handleHistoryForward: vi.fn(), + handleEdgeEnter: vi.fn(), + handleEdgeLeave: vi.fn(), + handleEdgesChange: vi.fn(), + handleEdgeContextMenu: vi.fn(), + handleSelectionStart: vi.fn(), + handleSelectionChange: vi.fn(), + handleSelectionDrag: vi.fn(), + handleSelectionContextMenu: vi.fn(), + handlePaneContextMenu: vi.fn(), + handleSyncWorkflowDraft: vi.fn(), + fetchInspectVars: vi.fn(), + isValidConnection: vi.fn(), + useShortcuts: vi.fn(), + useWorkflowSearch: vi.fn(), +})) + +const baseNodes = [ + { + id: 'node-1', + type: 'custom', + position: { x: 0, y: 0 }, + data: {}, + }, +] as unknown as Node[] + +const baseEdges = [ + { + id: 'edge-1', + source: 'node-1', + target: 'node-2', + data: { sourceType: 'start', targetType: 'end' }, + }, +] as unknown as Edge[] + +const edgeChanges: EdgeChange[] = [{ id: 'edge-1', type: 'remove' }] + +function createMouseEvent() { + return { + preventDefault: vi.fn(), + clientX: 24, + clientY: 48, + } as unknown as React.MouseEvent +} + +vi.mock('next/dynamic', () => ({ + default: () => () => null, +})) + +vi.mock('reactflow', async () => { + const mod = await import('./reactflow-mock-state') + const base = mod.createReactFlowModuleMock() + const ReactFlowMock = (props: ReactFlowProps) => { + reactFlowState.lastProps = props + return React.createElement( + 'div', + { 'data-testid': 'reactflow-mock' }, + React.createElement('button', { + 'type': 'button', + 'aria-label': 'Emit edge mouse enter', + 'onClick': () => props.onEdgeMouseEnter?.(createMouseEvent(), baseEdges[0]), + }), + React.createElement('button', { + 'type': 'button', + 'aria-label': 'Emit edge mouse leave', + 'onClick': () => props.onEdgeMouseLeave?.(createMouseEvent(), baseEdges[0]), + }), + React.createElement('button', { + 'type': 'button', + 'aria-label': 'Emit edges change', + 'onClick': () => props.onEdgesChange?.(edgeChanges), + }), + React.createElement('button', { + 'type': 'button', + 'aria-label': 'Emit edge context menu', + 'onClick': () => props.onEdgeContextMenu?.(createMouseEvent(), baseEdges[0]), + }), + React.createElement('button', { + 'type': 'button', + 'aria-label': 'Emit node context menu', + 'onClick': () => props.onNodeContextMenu?.(createMouseEvent(), baseNodes[0]), + }), + React.createElement('button', { + 'type': 'button', + 'aria-label': 'Emit pane context menu', + 'onClick': () => props.onPaneContextMenu?.(createMouseEvent()), + }), + props.children, + ) + } + + return { + ...base, + SelectionMode: { + Partial: 'partial', + }, + ReactFlow: ReactFlowMock, + default: ReactFlowMock, + } +}) + +vi.mock('@/context/event-emitter', () => ({ + useEventEmitterContextContext: () => ({ + eventEmitter: { + useSubscription: (handler: (payload: WorkflowUpdateEvent) => void) => { + eventEmitterState.subscription = handler + }, + }, + }), +})) + +vi.mock('@/service/use-tools', () => ({ + useAllBuiltInTools: () => ({ data: [] }), + useAllCustomTools: () => ({ data: [] }), + useAllMCPTools: () => ({ data: [] }), + useAllWorkflowTools: () => ({ data: [] }), +})) + +vi.mock('@/service/workflow', () => ({ + fetchAllInspectVars: vi.fn().mockResolvedValue([]), +})) + +vi.mock('../candidate-node', () => ({ + default: () => null, +})) + +vi.mock('../custom-connection-line', () => ({ + default: () => null, +})) + +vi.mock('../custom-edge', () => ({ + default: () => null, +})) + +vi.mock('../help-line', () => ({ + default: () => null, +})) + +vi.mock('../edge-contextmenu', () => ({ + default: () => null, +})) + +vi.mock('../node-contextmenu', () => ({ + default: () => null, +})) + +vi.mock('../nodes', () => ({ + default: () => null, +})) + +vi.mock('../nodes/data-source-empty', () => ({ + default: () => null, +})) + +vi.mock('../nodes/iteration-start', () => ({ + default: () => null, +})) + +vi.mock('../nodes/loop-start', () => ({ + default: () => null, +})) + +vi.mock('../note-node', () => ({ + default: () => null, +})) + +vi.mock('../operator', () => ({ + default: () => null, +})) + +vi.mock('../operator/control', () => ({ + default: () => null, +})) + +vi.mock('../panel-contextmenu', () => ({ + default: () => null, +})) + +vi.mock('../selection-contextmenu', () => ({ + default: () => null, +})) + +vi.mock('../simple-node', () => ({ + default: () => null, +})) + +vi.mock('../syncing-data-modal', () => ({ + default: () => null, +})) + +vi.mock('../hooks', () => ({ + useEdgesInteractions: () => ({ + handleEdgeEnter: workflowHookMocks.handleEdgeEnter, + handleEdgeLeave: workflowHookMocks.handleEdgeLeave, + handleEdgesChange: workflowHookMocks.handleEdgesChange, + handleEdgeContextMenu: workflowHookMocks.handleEdgeContextMenu, + }), + useNodesInteractions: () => ({ + handleNodeDragStart: workflowHookMocks.handleNodeDragStart, + handleNodeDrag: workflowHookMocks.handleNodeDrag, + handleNodeDragStop: workflowHookMocks.handleNodeDragStop, + handleNodeEnter: workflowHookMocks.handleNodeEnter, + handleNodeLeave: workflowHookMocks.handleNodeLeave, + handleNodeClick: workflowHookMocks.handleNodeClick, + handleNodeConnect: workflowHookMocks.handleNodeConnect, + handleNodeConnectStart: workflowHookMocks.handleNodeConnectStart, + handleNodeConnectEnd: workflowHookMocks.handleNodeConnectEnd, + handleNodeContextMenu: workflowHookMocks.handleNodeContextMenu, + handleHistoryBack: workflowHookMocks.handleHistoryBack, + handleHistoryForward: workflowHookMocks.handleHistoryForward, + }), + useNodesReadOnly: () => ({ + nodesReadOnly: false, + getNodesReadOnly: () => false, + }), + useNodesSyncDraft: () => ({ + handleSyncWorkflowDraft: workflowHookMocks.handleSyncWorkflowDraft, + syncWorkflowDraftWhenPageClose: vi.fn(), + }), + usePanelInteractions: () => ({ + handlePaneContextMenu: workflowHookMocks.handlePaneContextMenu, + handleEdgeContextmenuCancel: vi.fn(), + }), + useSelectionInteractions: () => ({ + handleSelectionStart: workflowHookMocks.handleSelectionStart, + handleSelectionChange: workflowHookMocks.handleSelectionChange, + handleSelectionDrag: workflowHookMocks.handleSelectionDrag, + handleSelectionContextMenu: workflowHookMocks.handleSelectionContextMenu, + }), + useSetWorkflowVarsWithValue: () => ({ + fetchInspectVars: workflowHookMocks.fetchInspectVars, + }), + useShortcuts: workflowHookMocks.useShortcuts, + useWorkflow: () => ({ + isValidConnection: workflowHookMocks.isValidConnection, + }), + useWorkflowReadOnly: () => ({ + workflowReadOnly: false, + }), + useWorkflowRefreshDraft: () => ({ + handleRefreshWorkflowDraft: vi.fn(), + }), +})) + +vi.mock('../hooks/use-workflow-search', () => ({ + useWorkflowSearch: workflowHookMocks.useWorkflowSearch, +})) + +vi.mock('../nodes/_base/components/variable/use-match-schema-type', () => ({ + default: () => ({ + schemaTypeDefinitions: undefined, + }), +})) + +vi.mock('../workflow-history-store', () => ({ + WorkflowHistoryProvider: ({ children }: { children?: React.ReactNode }) => React.createElement(React.Fragment, null, children), +})) + +function renderSubject() { + return renderWorkflowComponent( + , + { + hooksStoreProps: { + configsMap: { + flowId: 'flow-1', + flowType: FlowType.appFlow, + fileSettings: {}, + }, + }, + }, + ) +} + +describe('Workflow edge event wiring', () => { + beforeEach(() => { + vi.clearAllMocks() + reactFlowState.lastProps = null + eventEmitterState.subscription = null + }) + + it('should forward React Flow edge events to workflow handlers when emitted by the canvas', () => { + renderSubject() + + fireEvent.click(screen.getByRole('button', { name: 'Emit edge mouse enter' })) + fireEvent.click(screen.getByRole('button', { name: 'Emit edge mouse leave' })) + fireEvent.click(screen.getByRole('button', { name: 'Emit edges change' })) + fireEvent.click(screen.getByRole('button', { name: 'Emit edge context menu' })) + fireEvent.click(screen.getByRole('button', { name: 'Emit node context menu' })) + fireEvent.click(screen.getByRole('button', { name: 'Emit pane context menu' })) + + expect(workflowHookMocks.handleEdgeEnter).toHaveBeenCalledWith(expect.objectContaining({ + clientX: 24, + clientY: 48, + }), baseEdges[0]) + expect(workflowHookMocks.handleEdgeLeave).toHaveBeenCalledWith(expect.objectContaining({ + clientX: 24, + clientY: 48, + }), baseEdges[0]) + expect(workflowHookMocks.handleEdgesChange).toHaveBeenCalledWith(edgeChanges) + expect(workflowHookMocks.handleEdgeContextMenu).toHaveBeenCalledWith(expect.objectContaining({ + clientX: 24, + clientY: 48, + }), baseEdges[0]) + expect(workflowHookMocks.handleNodeContextMenu).toHaveBeenCalledWith(expect.objectContaining({ + clientX: 24, + clientY: 48, + }), baseNodes[0]) + expect(workflowHookMocks.handlePaneContextMenu).toHaveBeenCalledWith(expect.objectContaining({ + clientX: 24, + clientY: 48, + })) + }) + + it('should keep edge deletion delegated to workflow shortcuts instead of React Flow defaults', () => { + renderSubject() + + expect(reactFlowState.lastProps?.deleteKeyCode).toBeNull() + }) + + it('should clear edgeMenu when workflow data updates remove the current edge', () => { + const { store } = renderWorkflowComponent( + , + { + initialStoreState: { + edgeMenu: { + clientX: 320, + clientY: 180, + edgeId: 'edge-1', + }, + }, + hooksStoreProps: { + configsMap: { + flowId: 'flow-1', + flowType: FlowType.appFlow, + fileSettings: {}, + }, + }, + }, + ) + + act(() => { + eventEmitterState.subscription?.({ + type: WORKFLOW_DATA_UPDATE, + payload: { + nodes: baseNodes, + edges: [], + }, + }) + }) + + expect(store.getState().edgeMenu).toBeUndefined() + }) +}) diff --git a/web/app/components/workflow/edge-contextmenu.spec.tsx b/web/app/components/workflow/edge-contextmenu.spec.tsx new file mode 100644 index 0000000000..c1b021e624 --- /dev/null +++ b/web/app/components/workflow/edge-contextmenu.spec.tsx @@ -0,0 +1,340 @@ +import { fireEvent, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useEffect } from 'react' +import { resetReactFlowMockState, rfState } from './__tests__/reactflow-mock-state' +import { renderWorkflowComponent } from './__tests__/workflow-test-env' +import EdgeContextmenu from './edge-contextmenu' +import { useEdgesInteractions } from './hooks/use-edges-interactions' + +vi.mock('reactflow', async () => + (await import('./__tests__/reactflow-mock-state')).createReactFlowModuleMock()) + +const mockSaveStateToHistory = vi.fn() + +vi.mock('./hooks/use-workflow-history', () => ({ + useWorkflowHistory: () => ({ saveStateToHistory: mockSaveStateToHistory }), + WorkflowHistoryEvent: { + EdgeDelete: 'EdgeDelete', + EdgeDeleteByDeleteBranch: 'EdgeDeleteByDeleteBranch', + EdgeSourceHandleChange: 'EdgeSourceHandleChange', + }, +})) + +vi.mock('./hooks/use-workflow', () => ({ + useNodesReadOnly: () => ({ + getNodesReadOnly: () => false, + }), +})) + +vi.mock('./utils', async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + getNodesConnectedSourceOrTargetHandleIdsMap: vi.fn(() => ({})), + } +}) + +vi.mock('./hooks', async () => { + const { useEdgesInteractions } = await import('./hooks/use-edges-interactions') + const { usePanelInteractions } = await import('./hooks/use-panel-interactions') + + return { + useEdgesInteractions, + usePanelInteractions, + } +}) + +describe('EdgeContextmenu', () => { + const hooksStoreProps = { + doSyncWorkflowDraft: vi.fn().mockResolvedValue(undefined), + } + type TestNode = typeof rfState.nodes[number] & { + selected?: boolean + data: { + selected?: boolean + _isBundled?: boolean + } + } + type TestEdge = typeof rfState.edges[number] & { + selected?: boolean + } + const createNode = (id: string, selected = false): TestNode => ({ + id, + position: { x: 0, y: 0 }, + data: { selected }, + selected, + }) + const createEdge = (id: string, selected = false): TestEdge => ({ + id, + source: 'n1', + target: 'n2', + data: {}, + selected, + }) + + const EdgeMenuHarness = () => { + const { handleEdgeContextMenu, handleEdgeDelete } = useEdgesInteractions() + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key !== 'Delete' && e.key !== 'Backspace') + return + + e.preventDefault() + handleEdgeDelete() + } + + document.addEventListener('keydown', handleKeyDown) + return () => { + document.removeEventListener('keydown', handleKeyDown) + } + }, [handleEdgeDelete]) + + return ( +
+ + + +
+ ) + } + + beforeEach(() => { + vi.clearAllMocks() + resetReactFlowMockState() + rfState.nodes = [ + createNode('n1'), + createNode('n2'), + ] + rfState.edges = [ + createEdge('e1', true) as typeof rfState.edges[number] & { selected: boolean }, + createEdge('e2'), + ] + rfState.setNodes.mockImplementation((nextNodes) => { + rfState.nodes = nextNodes as typeof rfState.nodes + }) + rfState.setEdges.mockImplementation((nextEdges) => { + rfState.edges = nextEdges as typeof rfState.edges + }) + }) + + it('should not render when edgeMenu is absent', () => { + renderWorkflowComponent(, { + hooksStoreProps, + }) + + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + + it('should delete the menu edge and close the menu when another edge is selected', async () => { + const user = userEvent.setup() + ;(rfState.edges[0] as Record).selected = true + ;(rfState.edges[1] as Record).selected = false + + const { store } = renderWorkflowComponent(, { + initialStoreState: { + edgeMenu: { + clientX: 320, + clientY: 180, + edgeId: 'e2', + }, + }, + hooksStoreProps, + }) + + const deleteAction = await screen.findByRole('menuitem', { name: /common:operation\.delete/i }) + expect(screen.getByText(/^del$/i)).toBeInTheDocument() + + await user.click(deleteAction) + + const updatedEdges = rfState.setEdges.mock.calls.at(-1)?.[0] + expect(updatedEdges).toHaveLength(1) + expect(updatedEdges[0].id).toBe('e1') + expect(updatedEdges[0].selected).toBe(true) + expect(mockSaveStateToHistory).toHaveBeenCalledWith('EdgeDelete') + + await waitFor(() => { + expect(store.getState().edgeMenu).toBeUndefined() + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + }) + + it('should not render the menu when the referenced edge no longer exists', () => { + renderWorkflowComponent(, { + initialStoreState: { + edgeMenu: { + clientX: 320, + clientY: 180, + edgeId: 'missing-edge', + }, + }, + hooksStoreProps, + }) + + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + + it('should open the edge menu at the right-click position', async () => { + const fromRectSpy = vi.spyOn(DOMRect, 'fromRect') + + renderWorkflowComponent(, { + hooksStoreProps, + }) + + fireEvent.contextMenu(screen.getByRole('button', { name: 'Right-click edge e2' }), { + clientX: 320, + clientY: 180, + }) + + expect(await screen.findByRole('menu')).toBeInTheDocument() + expect(screen.getByRole('menuitem', { name: /common:operation\.delete/i })).toBeInTheDocument() + expect(fromRectSpy).toHaveBeenLastCalledWith(expect.objectContaining({ + x: 320, + y: 180, + width: 0, + height: 0, + })) + }) + + it('should delete the right-clicked edge and close the menu when delete is clicked', async () => { + const user = userEvent.setup() + + renderWorkflowComponent(, { + hooksStoreProps, + }) + + fireEvent.contextMenu(screen.getByRole('button', { name: 'Right-click edge e2' }), { + clientX: 320, + clientY: 180, + }) + + await user.click(await screen.findByRole('menuitem', { name: /common:operation\.delete/i })) + + await waitFor(() => { + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + expect(rfState.edges.map(edge => edge.id)).toEqual(['e1']) + expect(mockSaveStateToHistory).toHaveBeenCalledWith('EdgeDelete') + }) + + it.each([ + ['Delete', 'Delete'], + ['Backspace', 'Backspace'], + ])('should delete the right-clicked edge with %s after switching from a selected node', async (_, key) => { + renderWorkflowComponent(, { + hooksStoreProps, + }) + rfState.nodes = [createNode('n1', true), createNode('n2')] + + fireEvent.contextMenu(screen.getByRole('button', { name: 'Right-click edge e2' }), { + clientX: 240, + clientY: 120, + }) + + expect(await screen.findByRole('menu')).toBeInTheDocument() + + fireEvent.keyDown(document, { key }) + + await waitFor(() => { + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + expect(rfState.edges.map(edge => edge.id)).toEqual(['e1']) + expect(rfState.nodes.map(node => node.id)).toEqual(['n1', 'n2']) + expect((rfState.nodes as TestNode[]).every(node => !node.selected && !node.data.selected)).toBe(true) + }) + + it('should keep bundled multi-selection nodes intact when delete runs after right-clicking an edge', async () => { + renderWorkflowComponent(, { + hooksStoreProps, + }) + rfState.nodes = [ + { ...createNode('n1', true), data: { selected: true, _isBundled: true } }, + { ...createNode('n2', true), data: { selected: true, _isBundled: true } }, + ] + + fireEvent.contextMenu(screen.getByRole('button', { name: 'Right-click edge e1' }), { + clientX: 200, + clientY: 100, + }) + + expect(await screen.findByRole('menu')).toBeInTheDocument() + + fireEvent.keyDown(document, { key: 'Delete' }) + + await waitFor(() => { + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + expect(rfState.edges.map(edge => edge.id)).toEqual(['e2']) + expect(rfState.nodes).toHaveLength(2) + expect((rfState.nodes as TestNode[]).every(node => !node.selected && !node.data.selected && !node.data._isBundled)).toBe(true) + }) + + it('should retarget the menu and selected edge when right-clicking a different edge', async () => { + const fromRectSpy = vi.spyOn(DOMRect, 'fromRect') + + renderWorkflowComponent(, { + hooksStoreProps, + }) + const edgeOneButton = screen.getByLabelText('Right-click edge e1') + const edgeTwoButton = screen.getByLabelText('Right-click edge e2') + + fireEvent.contextMenu(edgeOneButton, { + clientX: 80, + clientY: 60, + }) + expect(await screen.findByRole('menu')).toBeInTheDocument() + + fireEvent.contextMenu(edgeTwoButton, { + clientX: 360, + clientY: 240, + }) + + await waitFor(() => { + expect(screen.getAllByRole('menu')).toHaveLength(1) + expect(fromRectSpy).toHaveBeenLastCalledWith(expect.objectContaining({ + x: 360, + y: 240, + })) + expect((rfState.edges as TestEdge[]).find(edge => edge.id === 'e1')?.selected).toBe(false) + expect((rfState.edges as TestEdge[]).find(edge => edge.id === 'e2')?.selected).toBe(true) + }) + }) + + it('should hide the menu when the target edge disappears after opening it', async () => { + const { store } = renderWorkflowComponent(, { + hooksStoreProps, + }) + + fireEvent.contextMenu(screen.getByRole('button', { name: 'Right-click edge e1' }), { + clientX: 160, + clientY: 100, + }) + expect(await screen.findByRole('menu')).toBeInTheDocument() + + rfState.edges = [createEdge('e2')] + store.setState({ + edgeMenu: { + clientX: 160, + clientY: 100, + edgeId: 'e1', + }, + }) + + await waitFor(() => { + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/workflow/edge-contextmenu.tsx b/web/app/components/workflow/edge-contextmenu.tsx new file mode 100644 index 0000000000..254df5165b --- /dev/null +++ b/web/app/components/workflow/edge-contextmenu.tsx @@ -0,0 +1,62 @@ +import { + memo, + useMemo, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useEdges } from 'reactflow' +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, +} from '@/app/components/base/ui/context-menu' +import { useEdgesInteractions, usePanelInteractions } from './hooks' +import ShortcutsName from './shortcuts-name' +import { useStore } from './store' + +const EdgeContextmenu = () => { + const { t } = useTranslation() + const edgeMenu = useStore(s => s.edgeMenu) + const { handleEdgeDeleteById } = useEdgesInteractions() + const { handleEdgeContextmenuCancel } = usePanelInteractions() + const edges = useEdges() + const currentEdgeExists = !edgeMenu || edges.some(edge => edge.id === edgeMenu.edgeId) + + const anchor = useMemo(() => { + if (!edgeMenu || !currentEdgeExists) + return null + + return { + getBoundingClientRect: () => DOMRect.fromRect({ + width: 0, + height: 0, + x: edgeMenu.clientX, + y: edgeMenu.clientY, + }), + } + }, [currentEdgeExists, edgeMenu]) + + if (!edgeMenu || !currentEdgeExists || !anchor) + return null + + return ( + !open && handleEdgeContextmenuCancel()} + > + + handleEdgeDeleteById(edgeMenu.edgeId)} + > + {t('common:operation.delete')} + + + + + ) +} + +export default memo(EdgeContextmenu) diff --git a/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts b/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts index 6d19862efd..c596be0a4b 100644 --- a/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-edges-interactions.spec.ts @@ -83,15 +83,56 @@ describe('useEdgesInteractions', () => { expect(updated.find((e: { id: string }) => e.id === 'e2').selected).toBe(false) }) + it('handleEdgeContextMenu should select the clicked edge and open edgeMenu', () => { + const preventDefault = vi.fn() + const { result, store } = renderEdgesInteractions() + rfState.nodes = [ + { id: 'n1', position: { x: 0, y: 0 }, data: { selected: true, _isBundled: true }, selected: true } as typeof rfState.nodes[number] & { selected: boolean }, + { id: 'n2', position: { x: 100, y: 0 }, data: { _isBundled: true } }, + ] + rfState.edges = [ + { id: 'e1', source: 'n1', target: 'n2', sourceHandle: 'branch-a', data: { _hovering: false, _isBundled: true } }, + { id: 'e2', source: 'n1', target: 'n2', sourceHandle: 'branch-b', data: { _hovering: false, _isBundled: true } }, + ] + + result.current.handleEdgeContextMenu({ + preventDefault, + clientX: 320, + clientY: 180, + } as never, rfState.edges[1] as never) + + expect(preventDefault).toHaveBeenCalled() + + const updated = rfState.setEdges.mock.calls[0][0] + expect(updated.find((e: { id: string }) => e.id === 'e1').selected).toBe(false) + expect(updated.find((e: { id: string }) => e.id === 'e2').selected).toBe(true) + expect(updated.every((e: { data: { _isBundled?: boolean } }) => !e.data._isBundled)).toBe(true) + const updatedNodes = rfState.setNodes.mock.calls[0][0] + expect(updatedNodes.every((node: { data: { selected?: boolean, _isBundled?: boolean }, selected?: boolean }) => !node.data.selected && !node.selected && !node.data._isBundled)).toBe(true) + + expect(store.getState().edgeMenu).toEqual({ + clientX: 320, + clientY: 180, + edgeId: 'e2', + }) + expect(store.getState().nodeMenu).toBeUndefined() + expect(store.getState().panelMenu).toBeUndefined() + expect(store.getState().selectionMenu).toBeUndefined() + }) + it('handleEdgeDelete should remove selected edge and trigger sync + history', () => { ;(rfState.edges[0] as Record).selected = true - const { result } = renderEdgesInteractions() + const { result, store } = renderEdgesInteractions() + store.setState({ + edgeMenu: { clientX: 320, clientY: 180, edgeId: 'e1' }, + }) result.current.handleEdgeDelete() const updated = rfState.setEdges.mock.calls[0][0] expect(updated).toHaveLength(1) expect(updated[0].id).toBe('e2') + expect(store.getState().edgeMenu).toBeUndefined() expect(mockSaveStateToHistory).toHaveBeenCalledWith('EdgeDelete') }) @@ -101,13 +142,34 @@ describe('useEdgesInteractions', () => { expect(rfState.setEdges).not.toHaveBeenCalled() }) + it('handleEdgeDeleteById should remove the requested edge even when another edge is selected', () => { + ;(rfState.edges[0] as Record).selected = true + const { result, store } = renderEdgesInteractions() + store.setState({ + edgeMenu: { clientX: 320, clientY: 180, edgeId: 'e2' }, + }) + + result.current.handleEdgeDeleteById('e2') + + const updated = rfState.setEdges.mock.calls[0][0] + expect(updated).toHaveLength(1) + expect(updated[0].id).toBe('e1') + expect(updated[0].selected).toBe(true) + expect(store.getState().edgeMenu).toBeUndefined() + expect(mockSaveStateToHistory).toHaveBeenCalledWith('EdgeDelete') + }) + it('handleEdgeDeleteByDeleteBranch should remove edges for the given branch', () => { - const { result } = renderEdgesInteractions() + const { result, store } = renderEdgesInteractions() + store.setState({ + edgeMenu: { clientX: 320, clientY: 180, edgeId: 'e1' }, + }) result.current.handleEdgeDeleteByDeleteBranch('n1', 'branch-a') const updated = rfState.setEdges.mock.calls[0][0] expect(updated).toHaveLength(1) expect(updated[0].id).toBe('e2') + expect(store.getState().edgeMenu).toBeUndefined() expect(mockSaveStateToHistory).toHaveBeenCalledWith('EdgeDeleteByDeleteBranch') }) @@ -142,6 +204,23 @@ describe('useEdgesInteractions', () => { expect(rfState.setEdges).not.toHaveBeenCalled() }) + it('handleEdgeDeleteById should do nothing', () => { + const { result } = renderEdgesInteractions() + result.current.handleEdgeDeleteById('e1') + expect(rfState.setEdges).not.toHaveBeenCalled() + }) + + it('handleEdgeContextMenu should do nothing', () => { + const { result, store } = renderEdgesInteractions() + result.current.handleEdgeContextMenu({ + preventDefault: vi.fn(), + clientX: 200, + clientY: 120, + } as never, rfState.edges[0] as never) + expect(rfState.setEdges).not.toHaveBeenCalled() + expect(store.getState().edgeMenu).toBeUndefined() + }) + it('handleEdgeDeleteByDeleteBranch should do nothing', () => { const { result } = renderEdgesInteractions() result.current.handleEdgeDeleteByDeleteBranch('n1', 'branch-a') diff --git a/web/app/components/workflow/hooks/__tests__/use-panel-interactions.spec.ts b/web/app/components/workflow/hooks/__tests__/use-panel-interactions.spec.ts index ec689f23f9..517af513b9 100644 --- a/web/app/components/workflow/hooks/__tests__/use-panel-interactions.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-panel-interactions.spec.ts @@ -26,7 +26,13 @@ describe('usePanelInteractions', () => { }) it('handlePaneContextMenu should set panelMenu with computed coordinates when container exists', () => { - const { result, store } = renderWorkflowHook(() => usePanelInteractions()) + const { result, store } = renderWorkflowHook(() => usePanelInteractions(), { + initialStoreState: { + nodeMenu: { top: 20, left: 40, nodeId: 'n1' }, + selectionMenu: { top: 30, left: 50 }, + edgeMenu: { clientX: 320, clientY: 180, edgeId: 'e1' }, + }, + }) const preventDefault = vi.fn() result.current.handlePaneContextMenu({ @@ -40,6 +46,9 @@ describe('usePanelInteractions', () => { top: 200, left: 250, }) + expect(store.getState().nodeMenu).toBeUndefined() + expect(store.getState().selectionMenu).toBeUndefined() + expect(store.getState().edgeMenu).toBeUndefined() }) it('handlePaneContextMenu should throw when container does not exist', () => { @@ -75,4 +84,14 @@ describe('usePanelInteractions', () => { expect(store.getState().nodeMenu).toBeUndefined() }) + + it('handleEdgeContextmenuCancel should clear edgeMenu', () => { + const { result, store } = renderWorkflowHook(() => usePanelInteractions(), { + initialStoreState: { edgeMenu: { clientX: 300, clientY: 200, edgeId: 'e1' } }, + }) + + result.current.handleEdgeContextmenuCancel() + + expect(store.getState().edgeMenu).toBeUndefined() + }) }) diff --git a/web/app/components/workflow/hooks/__tests__/use-selection-interactions.spec.ts b/web/app/components/workflow/hooks/__tests__/use-selection-interactions.spec.ts index 7e65176e6f..4c4eb010e6 100644 --- a/web/app/components/workflow/hooks/__tests__/use-selection-interactions.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-selection-interactions.spec.ts @@ -150,7 +150,13 @@ describe('useSelectionInteractions', () => { }) it('handleSelectionContextMenu should set menu only when clicking on selection rect', () => { - const { result, store } = renderWorkflowHook(() => useSelectionInteractions()) + const { result, store } = renderWorkflowHook(() => useSelectionInteractions(), { + initialStoreState: { + nodeMenu: { top: 10, left: 20, nodeId: 'n1' }, + panelMenu: { top: 30, left: 40 }, + edgeMenu: { clientX: 320, clientY: 180, edgeId: 'e1' }, + }, + }) const wrongTarget = document.createElement('div') wrongTarget.classList.add('some-other-class') @@ -176,6 +182,9 @@ describe('useSelectionInteractions', () => { top: 150, left: 200, }) + expect(store.getState().nodeMenu).toBeUndefined() + expect(store.getState().panelMenu).toBeUndefined() + expect(store.getState().edgeMenu).toBeUndefined() }) it('handleSelectionContextmenuCancel should clear selectionMenu', () => { diff --git a/web/app/components/workflow/hooks/use-edges-interactions.ts b/web/app/components/workflow/hooks/use-edges-interactions.ts index 0e911c3de8..484e552ba2 100644 --- a/web/app/components/workflow/hooks/use-edges-interactions.ts +++ b/web/app/components/workflow/hooks/use-edges-interactions.ts @@ -10,6 +10,7 @@ import { useCallback } from 'react' import { useStoreApi, } from 'reactflow' +import { useWorkflowStore } from '../store' import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../utils' import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useNodesReadOnly } from './use-workflow' @@ -17,10 +18,52 @@ import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history export const useEdgesInteractions = () => { const store = useStoreApi() + const workflowStore = useWorkflowStore() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { getNodesReadOnly } = useNodesReadOnly() const { saveStateToHistory } = useWorkflowHistory() + const deleteEdgeById = useCallback((edgeId: string) => { + const { + getNodes, + setNodes, + edges, + setEdges, + } = store.getState() + const currentEdgeIndex = edges.findIndex(edge => edge.id === edgeId) + + if (currentEdgeIndex < 0) + return + const currentEdge = edges[currentEdgeIndex] + const nodes = getNodes() + const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap( + [ + { type: 'remove', edge: currentEdge }, + ], + nodes, + ) + const newNodes = produce(nodes, (draft: Node[]) => { + draft.forEach((node) => { + if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) { + node.data = { + ...node.data, + ...nodesConnectedSourceOrTargetHandleIdsMap[node.id], + } + } + }) + }) + setNodes(newNodes) + const newEdges = produce(edges, (draft) => { + draft.splice(currentEdgeIndex, 1) + }) + setEdges(newEdges) + const currentEdgeMenu = workflowStore.getState().edgeMenu + if (currentEdgeMenu?.edgeId === currentEdge.id) + workflowStore.setState({ edgeMenu: undefined }) + handleSyncWorkflowDraft() + saveStateToHistory(WorkflowHistoryEvent.EdgeDelete) + }, [store, workflowStore, handleSyncWorkflowDraft, saveStateToHistory]) + const handleEdgeEnter = useCallback((_, edge) => { if (getNodesReadOnly()) return @@ -88,50 +131,31 @@ export const useEdgesInteractions = () => { return draft.filter(edge => !edgeWillBeDeleted.find(e => e.id === edge.id)) }) setEdges(newEdges) + const currentEdgeMenu = workflowStore.getState().edgeMenu + if (currentEdgeMenu && edgeWillBeDeleted.some(edge => edge.id === currentEdgeMenu.edgeId)) + workflowStore.setState({ edgeMenu: undefined }) handleSyncWorkflowDraft() saveStateToHistory(WorkflowHistoryEvent.EdgeDeleteByDeleteBranch) - }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory]) + }, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, saveStateToHistory]) const handleEdgeDelete = useCallback(() => { if (getNodesReadOnly()) return + const { edges } = store.getState() + const currentEdge = edges.find(edge => edge.selected) - const { - getNodes, - setNodes, - edges, - setEdges, - } = store.getState() - const currentEdgeIndex = edges.findIndex(edge => edge.selected) - - if (currentEdgeIndex < 0) + if (!currentEdge) return - const currentEdge = edges[currentEdgeIndex] - const nodes = getNodes() - const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap( - [ - { type: 'remove', edge: currentEdge }, - ], - nodes, - ) - const newNodes = produce(nodes, (draft: Node[]) => { - draft.forEach((node) => { - if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) { - node.data = { - ...node.data, - ...nodesConnectedSourceOrTargetHandleIdsMap[node.id], - } - } - }) - }) - setNodes(newNodes) - const newEdges = produce(edges, (draft) => { - draft.splice(currentEdgeIndex, 1) - }) - setEdges(newEdges) - handleSyncWorkflowDraft() - saveStateToHistory(WorkflowHistoryEvent.EdgeDelete) - }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory]) + + deleteEdgeById(currentEdge.id) + }, [deleteEdgeById, getNodesReadOnly, store]) + + const handleEdgeDeleteById = useCallback((edgeId: string) => { + if (getNodesReadOnly()) + return + + deleteEdgeById(edgeId) + }, [deleteEdgeById, getNodesReadOnly]) const handleEdgesChange = useCallback((changes) => { if (getNodesReadOnly()) @@ -200,16 +224,61 @@ export const useEdgesInteractions = () => { }) }) setEdges(newEdges) + const currentEdgeMenu = workflowStore.getState().edgeMenu + if (currentEdgeMenu && !newEdges.some(edge => edge.id === currentEdgeMenu.edgeId)) + workflowStore.setState({ edgeMenu: undefined }) handleSyncWorkflowDraft() saveStateToHistory(WorkflowHistoryEvent.EdgeSourceHandleChange) - }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory]) + }, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, saveStateToHistory]) + + const handleEdgeContextMenu = useCallback((e, edge) => { + if (getNodesReadOnly()) + return + + e.preventDefault() + + const { getNodes, setNodes, edges, setEdges } = store.getState() + const newEdges = produce(edges, (draft) => { + draft.forEach((item) => { + item.selected = item.id === edge.id + if (item.data._isBundled) + item.data._isBundled = false + }) + }) + setEdges(newEdges) + const nodes = getNodes() + if (nodes.some(node => node.data.selected || node.selected || node.data._isBundled)) { + const newNodes = produce(nodes, (draft: Node[]) => { + draft.forEach((node) => { + node.data.selected = false + if (node.data._isBundled) + node.data._isBundled = false + node.selected = false + }) + }) + setNodes(newNodes) + } + + workflowStore.setState({ + nodeMenu: undefined, + panelMenu: undefined, + selectionMenu: undefined, + edgeMenu: { + clientX: e.clientX, + clientY: e.clientY, + edgeId: edge.id, + }, + }) + }, [store, workflowStore, getNodesReadOnly]) return { handleEdgeEnter, handleEdgeLeave, handleEdgeDeleteByDeleteBranch, handleEdgeDelete, + handleEdgeDeleteById, handleEdgesChange, handleEdgeSourceHandleChange, + handleEdgeContextMenu, } } diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index e18405b196..b0dd43eb1d 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -1642,6 +1642,9 @@ export const useNodesInteractions = () => { const container = document.querySelector('#workflow-container') const { x, y } = container!.getBoundingClientRect() workflowStore.setState({ + panelMenu: undefined, + selectionMenu: undefined, + edgeMenu: undefined, nodeMenu: { top: e.clientY - y, left: e.clientX - x, @@ -2098,7 +2101,9 @@ export const useNodesInteractions = () => { setEdges(edges) setNodes(nodes) + workflowStore.setState({ edgeMenu: undefined }) }, [ + workflowStore, store, undo, workflowHistoryStore, @@ -2119,9 +2124,11 @@ export const useNodesInteractions = () => { setEdges(edges) setNodes(nodes) + workflowStore.setState({ edgeMenu: undefined }) }, [ redo, store, + workflowStore, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly, diff --git a/web/app/components/workflow/hooks/use-panel-interactions.ts b/web/app/components/workflow/hooks/use-panel-interactions.ts index 1f02ac7c74..469a7abdee 100644 --- a/web/app/components/workflow/hooks/use-panel-interactions.ts +++ b/web/app/components/workflow/hooks/use-panel-interactions.ts @@ -10,6 +10,9 @@ export const usePanelInteractions = () => { const container = document.querySelector('#workflow-container') const { x, y } = container!.getBoundingClientRect() workflowStore.setState({ + nodeMenu: undefined, + selectionMenu: undefined, + edgeMenu: undefined, panelMenu: { top: e.clientY - y, left: e.clientX - x, @@ -29,9 +32,16 @@ export const usePanelInteractions = () => { }) }, [workflowStore]) + const handleEdgeContextmenuCancel = useCallback(() => { + workflowStore.setState({ + edgeMenu: undefined, + }) + }, [workflowStore]) + return { handlePaneContextMenu, handlePaneContextmenuCancel, handleNodeContextmenuCancel, + handleEdgeContextmenuCancel, } } diff --git a/web/app/components/workflow/hooks/use-selection-interactions.ts b/web/app/components/workflow/hooks/use-selection-interactions.ts index dbdef306c5..3c05d64cc4 100644 --- a/web/app/components/workflow/hooks/use-selection-interactions.ts +++ b/web/app/components/workflow/hooks/use-selection-interactions.ts @@ -140,6 +140,9 @@ export const useSelectionInteractions = () => { const container = document.querySelector('#workflow-container') const { x, y } = container!.getBoundingClientRect() workflowStore.setState({ + nodeMenu: undefined, + panelMenu: undefined, + edgeMenu: undefined, selectionMenu: { top: e.clientY - y, left: e.clientX - x, diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 62516a797d..087368029f 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -55,6 +55,7 @@ import { import CustomConnectionLine from './custom-connection-line' import CustomEdge from './custom-edge' import DatasetsDetailProvider from './datasets-detail-store/provider' +import EdgeContextmenu from './edge-contextmenu' import HelpLine from './help-line' import { useEdgesInteractions, @@ -203,6 +204,7 @@ export const Workflow: FC = memo(({ setNodes(v.payload.nodes) store.getState().setNodes(v.payload.nodes) setEdges(v.payload.edges) + workflowStore.setState({ edgeMenu: undefined }) if (v.payload.viewport) reactflow.setViewport(v.payload.viewport) @@ -306,6 +308,7 @@ export const Workflow: FC = memo(({ handleEdgeEnter, handleEdgeLeave, handleEdgesChange, + handleEdgeContextMenu, } = useEdgesInteractions() const { handleSelectionStart, @@ -401,6 +404,7 @@ export const Workflow: FC = memo(({ + { @@ -433,6 +437,7 @@ export const Workflow: FC = memo(({ onEdgeMouseEnter={handleEdgeEnter} onEdgeMouseLeave={handleEdgeLeave} onEdgesChange={handleEdgesChange} + onEdgeContextMenu={handleEdgeContextMenu} onSelectionStart={handleSelectionStart} onSelectionChange={handleSelectionChange} onSelectionDrag={handleSelectionDrag} diff --git a/web/app/components/workflow/node-contextmenu.tsx b/web/app/components/workflow/node-contextmenu.tsx index cd749fefc0..b02fbc5a6e 100644 --- a/web/app/components/workflow/node-contextmenu.tsx +++ b/web/app/components/workflow/node-contextmenu.tsx @@ -2,7 +2,6 @@ import type { Node } from './types' import { useClickAway } from 'ahooks' import { memo, - useEffect, useRef, } from 'react' import useNodes from '@/app/components/workflow/store/workflow/use-nodes' @@ -13,13 +12,9 @@ import { useStore } from './store' const NodeContextmenu = () => { const ref = useRef(null) const nodes = useNodes() - const { handleNodeContextmenuCancel, handlePaneContextmenuCancel } = usePanelInteractions() + const { handleNodeContextmenuCancel } = usePanelInteractions() const nodeMenu = useStore(s => s.nodeMenu) const currentNode = nodes.find(node => node.id === nodeMenu?.nodeId) as Node - useEffect(() => { - if (nodeMenu) - handlePaneContextmenuCancel() - }, [nodeMenu, handlePaneContextmenuCancel]) useClickAway(() => { handleNodeContextmenuCancel() diff --git a/web/app/components/workflow/panel-contextmenu.tsx b/web/app/components/workflow/panel-contextmenu.tsx index d21d5f1dbc..41b0fbfbad 100644 --- a/web/app/components/workflow/panel-contextmenu.tsx +++ b/web/app/components/workflow/panel-contextmenu.tsx @@ -1,7 +1,6 @@ import { useClickAway } from 'ahooks' import { memo, - useEffect, useRef, } from 'react' import { useTranslation } from 'react-i18next' @@ -25,16 +24,11 @@ const PanelContextmenu = () => { const clipboardElements = useStore(s => s.clipboardElements) const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal) const { handleNodesPaste } = useNodesInteractions() - const { handlePaneContextmenuCancel, handleNodeContextmenuCancel } = usePanelInteractions() + const { handlePaneContextmenuCancel } = usePanelInteractions() const { handleStartWorkflowRun } = useWorkflowStartRun() const { handleAddNote } = useOperator() const { exportCheck } = useDSL() - useEffect(() => { - if (panelMenu) - handleNodeContextmenuCancel() - }, [panelMenu, handleNodeContextmenuCancel]) - useClickAway(() => { handlePaneContextmenuCancel() }, ref) diff --git a/web/app/components/workflow/store/__tests__/workflow-store.spec.ts b/web/app/components/workflow/store/__tests__/workflow-store.spec.ts index c917986953..df0288ac09 100644 --- a/web/app/components/workflow/store/__tests__/workflow-store.spec.ts +++ b/web/app/components/workflow/store/__tests__/workflow-store.spec.ts @@ -97,6 +97,7 @@ describe('createWorkflowStore', () => { ['showDebugAndPreviewPanel', 'setShowDebugAndPreviewPanel', true], ['panelMenu', 'setPanelMenu', { top: 10, left: 20 }], ['selectionMenu', 'setSelectionMenu', { top: 50, left: 60 }], + ['edgeMenu', 'setEdgeMenu', { clientX: 320, clientY: 180, edgeId: 'e1' }], ['showVariableInspectPanel', 'setShowVariableInspectPanel', true], ['initShowLastRunTab', 'setInitShowLastRunTab', true], ])('should update %s', (stateKey, setter, value) => { diff --git a/web/app/components/workflow/store/workflow/panel-slice.ts b/web/app/components/workflow/store/workflow/panel-slice.ts index 4848beeac5..bf8b248c3a 100644 --- a/web/app/components/workflow/store/workflow/panel-slice.ts +++ b/web/app/components/workflow/store/workflow/panel-slice.ts @@ -20,6 +20,12 @@ export type PanelSliceShape = { left: number } setSelectionMenu: (selectionMenu: PanelSliceShape['selectionMenu']) => void + edgeMenu?: { + clientX: number + clientY: number + edgeId: string + } + setEdgeMenu: (edgeMenu: PanelSliceShape['edgeMenu']) => void showVariableInspectPanel: boolean setShowVariableInspectPanel: (showVariableInspectPanel: boolean) => void initShowLastRunTab: boolean @@ -40,6 +46,8 @@ export const createPanelSlice: StateCreator = set => ({ setPanelMenu: panelMenu => set(() => ({ panelMenu })), selectionMenu: undefined, setSelectionMenu: selectionMenu => set(() => ({ selectionMenu })), + edgeMenu: undefined, + setEdgeMenu: edgeMenu => set(() => ({ edgeMenu })), showVariableInspectPanel: false, setShowVariableInspectPanel: showVariableInspectPanel => set(() => ({ showVariableInspectPanel })), initShowLastRunTab: false, From 97776eabffff2dbed44517a4206b259a2b592a08 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 13 Mar 2026 15:29:24 +0800 Subject: [PATCH 04/37] chore: add tracking info of in site message (#33394) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../app/in-site-message/index.spec.tsx | 20 ++++++++++++++----- .../components/app/in-site-message/index.tsx | 16 ++++++++++++++- .../app/in-site-message/notification.spec.tsx | 15 +++++++++----- .../app/in-site-message/notification.tsx | 2 ++ 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/web/app/components/app/in-site-message/index.spec.tsx b/web/app/components/app/in-site-message/index.spec.tsx index 69f036da17..530084074d 100644 --- a/web/app/components/app/in-site-message/index.spec.tsx +++ b/web/app/components/app/in-site-message/index.spec.tsx @@ -1,7 +1,13 @@ +import type { ComponentProps } from 'react' import type { InSiteMessageActionItem } from './index' import { fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import InSiteMessage from './index' +vi.mock('@/app/components/base/amplitude', () => ({ + trackEvent: vi.fn(), +})) + describe('InSiteMessage', () => { const originalLocation = window.location @@ -18,9 +24,10 @@ describe('InSiteMessage', () => { vi.unstubAllGlobals() }) - const renderComponent = (actions: InSiteMessageActionItem[], props?: Partial>) => { + const renderComponent = (actions: InSiteMessageActionItem[], props?: Partial>) => { return render( { describe('Rendering', () => { it('should render title, subtitle, markdown content, and action buttons', () => { const actions: InSiteMessageActionItem[] = [ - { action: 'close', text: 'Close', type: 'default' }, - { action: 'link', text: 'Learn more', type: 'primary', data: 'https://example.com' }, + { action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' }, + { action: 'link', action_name: 'learn_more', text: 'Learn more', type: 'primary', data: 'https://example.com' }, ] renderComponent(actions, { className: 'custom-message' }) @@ -56,7 +63,7 @@ describe('InSiteMessage', () => { }) it('should fallback to default header background when headerBgUrl is empty string', () => { - const actions: InSiteMessageActionItem[] = [{ action: 'close', text: 'Close', type: 'default' }] + const actions: InSiteMessageActionItem[] = [{ action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' }] const { container } = renderComponent(actions, { headerBgUrl: '' }) const header = container.querySelector('div[style]') @@ -68,7 +75,7 @@ describe('InSiteMessage', () => { describe('Actions', () => { it('should call onAction and hide component when close action is clicked', () => { const onAction = vi.fn() - const closeAction: InSiteMessageActionItem = { action: 'close', text: 'Close', type: 'default' } + const closeAction: InSiteMessageActionItem = { action: 'close', action_name: 'dismiss', text: 'Close', type: 'default' } renderComponent([closeAction], { onAction }) fireEvent.click(screen.getByRole('button', { name: 'Close' })) @@ -80,6 +87,7 @@ describe('InSiteMessage', () => { it('should open a new tab when link action data is a string', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Open link', type: 'primary', data: 'https://example.com', @@ -103,6 +111,7 @@ describe('InSiteMessage', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Open self', type: 'primary', data: { href: 'https://example.com/self', target: '_self' }, @@ -118,6 +127,7 @@ describe('InSiteMessage', () => { it('should not trigger navigation when link data is invalid', () => { const linkAction: InSiteMessageActionItem = { action: 'link', + action_name: 'confirm', text: 'Broken link', type: 'primary', data: { rel: 'noopener' }, diff --git a/web/app/components/app/in-site-message/index.tsx b/web/app/components/app/in-site-message/index.tsx index 9225eb8a15..0276257860 100644 --- a/web/app/components/app/in-site-message/index.tsx +++ b/web/app/components/app/in-site-message/index.tsx @@ -1,6 +1,7 @@ 'use client' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { trackEvent } from '@/app/components/base/amplitude' import Button from '@/app/components/base/button' import { MarkdownWithDirective } from '@/app/components/base/markdown-with-directive' import { cn } from '@/utils/classnames' @@ -10,12 +11,14 @@ type InSiteMessageButtonType = 'primary' | 'default' export type InSiteMessageActionItem = { action: InSiteMessageAction + action_name: string // for tracing and analytics data?: unknown text: string type: InSiteMessageButtonType } type InSiteMessageProps = { + notificationId: string actions: InSiteMessageActionItem[] className?: string headerBgUrl?: string @@ -52,6 +55,7 @@ function normalizeLinkData(data: unknown): { href: string, rel?: string, target? const DEFAULT_HEADER_BG_URL = '/in-site-message/header-bg.svg' function InSiteMessage({ + notificationId, actions, className, headerBgUrl = DEFAULT_HEADER_BG_URL, @@ -70,7 +74,17 @@ function InSiteMessage({ } }, [headerBgUrl]) + useEffect(() => { + trackEvent('in_site_message_show', { + notification_id: notificationId, + }) + }, [notificationId]) + const handleAction = (item: InSiteMessageActionItem) => { + trackEvent('in_site_message_action', { + notification_id: notificationId, + action: item.action_name, + }) onAction?.(item) if (item.action === 'close') { diff --git a/web/app/components/app/in-site-message/notification.spec.tsx b/web/app/components/app/in-site-message/notification.spec.tsx index 84fe3aebc7..0d86d8a91c 100644 --- a/web/app/components/app/in-site-message/notification.spec.tsx +++ b/web/app/components/app/in-site-message/notification.spec.tsx @@ -15,11 +15,16 @@ const { mockNotificationDismiss: vi.fn(), })) -vi.mock('@/config', () => ({ - get IS_CLOUD_EDITION() { - return mockConfig.isCloudEdition - }, -})) +vi.mock(import('@/config'), async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + get IS_CLOUD_EDITION() { + return mockConfig.isCloudEdition + }, + } +}) vi.mock('@/service/client', () => ({ consoleQuery: { diff --git a/web/app/components/app/in-site-message/notification.tsx b/web/app/components/app/in-site-message/notification.tsx index de256a4663..cebf6ffd91 100644 --- a/web/app/components/app/in-site-message/notification.tsx +++ b/web/app/components/app/in-site-message/notification.tsx @@ -75,6 +75,7 @@ function InSiteMessageNotification() { const fallbackActions: InSiteMessageActionItem[] = [ { type: 'default', + action_name: 'dismiss', text: t('operation.close', { ns: 'common' }), action: 'close', }, @@ -96,6 +97,7 @@ function InSiteMessageNotification() { return ( Date: Fri, 13 Mar 2026 15:35:02 +0800 Subject: [PATCH 05/37] fix: with_debug_recipient() silently drops debug emails when user_id is None or empty string (#33373) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Crazywoola <100913391+crazywoola@users.noreply.github.com> --- api/dify_graph/nodes/human_input/entities.py | 8 ++++---- api/services/workflow_service.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/dify_graph/nodes/human_input/entities.py b/api/dify_graph/nodes/human_input/entities.py index 82a8f32053..642c2143e5 100644 --- a/api/dify_graph/nodes/human_input/entities.py +++ b/api/dify_graph/nodes/human_input/entities.py @@ -72,8 +72,8 @@ class EmailDeliveryConfig(BaseModel): body: str debug_mode: bool = False - def with_debug_recipient(self, user_id: str) -> "EmailDeliveryConfig": - if not user_id: + def with_debug_recipient(self, user_id: str | None) -> "EmailDeliveryConfig": + if user_id is None: debug_recipients = EmailRecipients(whole_workspace=False, items=[]) return self.model_copy(update={"recipients": debug_recipients}) debug_recipients = EmailRecipients(whole_workspace=False, items=[MemberRecipient(user_id=user_id)]) @@ -141,7 +141,7 @@ def apply_debug_email_recipient( method: DeliveryChannelConfig, *, enabled: bool, - user_id: str, + user_id: str | None, ) -> DeliveryChannelConfig: if not enabled: return method @@ -149,7 +149,7 @@ def apply_debug_email_recipient( return method if not method.config.debug_mode: return method - debug_config = method.config.with_debug_recipient(user_id or "") + debug_config = method.config.with_debug_recipient(user_id) return method.model_copy(update={"config": debug_config}) diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index ec37fbeb6b..2549cdf180 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -952,7 +952,7 @@ class WorkflowService: delivery_method = apply_debug_email_recipient( delivery_method, enabled=True, - user_id=account.id or "", + user_id=account.id, ) variable_pool = self._build_human_input_variable_pool( From 00eda73ad109c024ca79384f69c8f64320c21464 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Fri, 13 Mar 2026 16:31:05 +0800 Subject: [PATCH 06/37] test: enforce app/components coverage gates in web tests (#33395) Co-authored-by: CodingOnStar --- .github/workflows/main-ci.yml | 3 + .github/workflows/web-tests.yml | 18 + .../component-coverage-filters.test.ts | 115 ++++ .../check-components-diff-coverage.mjs | 560 ++++++++++++++++++ web/scripts/component-coverage-filters.mjs | 316 ++++++++++ .../components-coverage-thresholds.mjs | 128 ++++ web/vite.config.ts | 24 + 7 files changed, 1164 insertions(+) create mode 100644 web/__tests__/component-coverage-filters.test.ts create mode 100644 web/scripts/check-components-diff-coverage.mjs create mode 100644 web/scripts/component-coverage-filters.mjs create mode 100644 web/scripts/components-coverage-thresholds.mjs diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml index ef2e3c7bb4..fd104e9496 100644 --- a/.github/workflows/main-ci.yml +++ b/.github/workflows/main-ci.yml @@ -62,6 +62,9 @@ jobs: needs: check-changes if: needs.check-changes.outputs.web-changed == 'true' uses: ./.github/workflows/web-tests.yml + with: + base_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + head_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} style-check: name: Style Check diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 7d2977245f..47cced863a 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -2,6 +2,13 @@ name: Web Tests on: workflow_call: + inputs: + base_sha: + required: false + type: string + head_sha: + required: false + type: string permissions: contents: read @@ -14,6 +21,8 @@ jobs: test: name: Web Tests (${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) runs-on: ubuntu-latest + env: + VITEST_COVERAGE_SCOPE: app-components strategy: fail-fast: false matrix: @@ -50,6 +59,8 @@ jobs: if: ${{ !cancelled() }} needs: [test] runs-on: ubuntu-latest + env: + VITEST_COVERAGE_SCOPE: app-components defaults: run: shell: bash @@ -59,6 +70,7 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + fetch-depth: 0 persist-credentials: false - name: Setup web environment @@ -74,6 +86,12 @@ jobs: - name: Merge reports run: pnpm vitest --merge-reports --reporter=json --reporter=agent --coverage + - name: Check app/components diff coverage + env: + BASE_SHA: ${{ inputs.base_sha }} + HEAD_SHA: ${{ inputs.head_sha }} + run: node ./scripts/check-components-diff-coverage.mjs + - name: Coverage Summary if: always() id: coverage-summary diff --git a/web/__tests__/component-coverage-filters.test.ts b/web/__tests__/component-coverage-filters.test.ts new file mode 100644 index 0000000000..cacc1e2142 --- /dev/null +++ b/web/__tests__/component-coverage-filters.test.ts @@ -0,0 +1,115 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import { + collectComponentCoverageExcludedFiles, + COMPONENT_COVERAGE_EXCLUDE_LABEL, + getComponentCoverageExclusionReasons, +} from '../scripts/component-coverage-filters.mjs' + +describe('component coverage filters', () => { + describe('getComponentCoverageExclusionReasons', () => { + it('should exclude type-only files by basename', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/share/text-generation/types.ts', + 'export type ShareMode = "run-once" | "run-batch"', + ), + ).toContain('type-only') + }) + + it('should exclude pure barrel files', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/base/amplitude/index.ts', + [ + 'export { default } from "./AmplitudeProvider"', + 'export { resetUser, trackEvent } from "./utils"', + ].join('\n'), + ), + ).toContain('pure-barrel') + }) + + it('should exclude generated files from marker comments', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/base/icons/src/vender/workflow/Answer.tsx', + [ + '// GENERATE BY script', + '// DON NOT EDIT IT MANUALLY', + 'export default function Icon() {', + ' return null', + '}', + ].join('\n'), + ), + ).toContain('generated') + }) + + it('should exclude pure static files with exported constants only', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/workflow/note-node/constants.ts', + [ + 'import { NoteTheme } from "./types"', + 'export const CUSTOM_NOTE_NODE = "custom-note"', + 'export const THEME_MAP = {', + ' [NoteTheme.blue]: { title: "bg-blue-100" },', + '}', + ].join('\n'), + ), + ).toContain('pure-static') + }) + + it('should keep runtime logic files tracked', () => { + expect( + getComponentCoverageExclusionReasons( + 'web/app/components/workflow/nodes/trigger-schedule/default.ts', + [ + 'const validate = (value: string) => value.trim()', + 'export const nodeDefault = {', + ' value: validate("x"),', + '}', + ].join('\n'), + ), + ).toEqual([]) + }) + }) + + describe('collectComponentCoverageExcludedFiles', () => { + const tempDirs: string[] = [] + + afterEach(() => { + for (const dir of tempDirs) + fs.rmSync(dir, { recursive: true, force: true }) + tempDirs.length = 0 + }) + + it('should collect excluded files for coverage config and keep runtime files out', () => { + const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'component-coverage-filters-')) + tempDirs.push(rootDir) + + fs.mkdirSync(path.join(rootDir, 'barrel'), { recursive: true }) + fs.mkdirSync(path.join(rootDir, 'icons'), { recursive: true }) + fs.mkdirSync(path.join(rootDir, 'static'), { recursive: true }) + fs.mkdirSync(path.join(rootDir, 'runtime'), { recursive: true }) + + fs.writeFileSync(path.join(rootDir, 'barrel', 'index.ts'), 'export { default } from "./Button"\n') + fs.writeFileSync(path.join(rootDir, 'icons', 'generated-icon.tsx'), '// @generated\nexport default function Icon() { return null }\n') + fs.writeFileSync(path.join(rootDir, 'static', 'constants.ts'), 'export const COLORS = { primary: "#fff" }\n') + fs.writeFileSync(path.join(rootDir, 'runtime', 'config.ts'), 'export const config = makeConfig()\n') + fs.writeFileSync(path.join(rootDir, 'runtime', 'types.ts'), 'export type Config = { value: string }\n') + + expect(collectComponentCoverageExcludedFiles(rootDir, { pathPrefix: 'app/components' })).toEqual([ + 'app/components/barrel/index.ts', + 'app/components/icons/generated-icon.tsx', + 'app/components/runtime/types.ts', + 'app/components/static/constants.ts', + ]) + }) + }) + + it('should describe the excluded coverage categories', () => { + expect(COMPONENT_COVERAGE_EXCLUDE_LABEL).toBe('type-only files, pure barrel files, generated files, pure static files') + }) +}) diff --git a/web/scripts/check-components-diff-coverage.mjs b/web/scripts/check-components-diff-coverage.mjs new file mode 100644 index 0000000000..429f97cb99 --- /dev/null +++ b/web/scripts/check-components-diff-coverage.mjs @@ -0,0 +1,560 @@ +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +import { + collectComponentCoverageExcludedFiles, + COMPONENT_COVERAGE_EXCLUDE_LABEL, +} from './component-coverage-filters.mjs' +import { + COMPONENTS_GLOBAL_THRESHOLDS, + EXCLUDED_COMPONENT_MODULES, + getComponentModuleThreshold, +} from './components-coverage-thresholds.mjs' + +const APP_COMPONENTS_PREFIX = 'web/app/components/' +const APP_COMPONENTS_COVERAGE_PREFIX = 'app/components/' +const SHARED_TEST_PREFIX = 'web/__tests__/' +const STRICT_TEST_FILE_TOUCH = process.env.STRICT_COMPONENT_TEST_TOUCH === 'true' +const EXCLUDED_MODULES_LABEL = [...EXCLUDED_COMPONENT_MODULES].sort().join(', ') + +const repoRoot = repoRootFromCwd() +const webRoot = path.join(repoRoot, 'web') +const excludedComponentCoverageFiles = new Set( + collectComponentCoverageExcludedFiles(path.join(webRoot, 'app/components'), { pathPrefix: 'web/app/components' }), +) +const baseSha = process.env.BASE_SHA?.trim() +const headSha = process.env.HEAD_SHA?.trim() || 'HEAD' +const coverageFinalPath = path.join(webRoot, 'coverage', 'coverage-final.json') + +if (!baseSha || /^0+$/.test(baseSha)) { + appendSummary([ + '### app/components Diff Coverage', + '', + 'Skipped diff coverage check because `BASE_SHA` was not available.', + ]) + process.exit(0) +} + +if (!fs.existsSync(coverageFinalPath)) { + console.error(`Coverage report not found at ${coverageFinalPath}`) + process.exit(1) +} + +const coverage = JSON.parse(fs.readFileSync(coverageFinalPath, 'utf8')) +const changedFiles = getChangedFiles(baseSha, headSha) +const changedComponentSourceFiles = changedFiles.filter(isAnyComponentSourceFile) +const changedSourceFiles = changedComponentSourceFiles.filter(isTrackedComponentSourceFile) +const changedExcludedSourceFiles = changedComponentSourceFiles.filter(isExcludedComponentSourceFile) +const changedTestFiles = changedFiles.filter(isRelevantTestFile) + +if (changedSourceFiles.length === 0) { + appendSummary(buildSkipSummary(changedExcludedSourceFiles)) + process.exit(0) +} + +const coverageEntries = new Map() +for (const [file, entry] of Object.entries(coverage)) { + const repoRelativePath = normalizeToRepoRelative(entry.path ?? file) + if (!isTrackedComponentSourceFile(repoRelativePath)) + continue + + coverageEntries.set(repoRelativePath, entry) +} + +const fileCoverageRows = [] +const moduleCoverageMap = new Map() + +for (const [file, entry] of coverageEntries.entries()) { + const stats = getCoverageStats(entry) + const moduleName = getModuleName(file) + fileCoverageRows.push({ file, moduleName, ...stats }) + mergeCoverageStats(moduleCoverageMap, moduleName, stats) +} + +const overallCoverage = sumCoverageStats(fileCoverageRows) +const diffChanges = getChangedLineMap(baseSha, headSha) +const diffRows = [] + +for (const [file, changedLines] of diffChanges.entries()) { + if (!isTrackedComponentSourceFile(file)) + continue + + const entry = coverageEntries.get(file) + const lineHits = entry ? getLineHits(entry) : {} + const executableChangedLines = [...changedLines] + .filter(line => !entry || lineHits[line] !== undefined) + .sort((a, b) => a - b) + + if (executableChangedLines.length === 0) { + diffRows.push({ + file, + moduleName: getModuleName(file), + total: 0, + covered: 0, + uncoveredLines: [], + }) + continue + } + + const uncoveredLines = executableChangedLines.filter(line => (lineHits[line] ?? 0) === 0) + diffRows.push({ + file, + moduleName: getModuleName(file), + total: executableChangedLines.length, + covered: executableChangedLines.length - uncoveredLines.length, + uncoveredLines, + }) +} + +const diffTotals = diffRows.reduce((acc, row) => { + acc.total += row.total + acc.covered += row.covered + return acc +}, { total: 0, covered: 0 }) + +const diffCoveragePct = percentage(diffTotals.covered, diffTotals.total) +const diffFailures = diffRows.filter(row => row.uncoveredLines.length > 0) +const overallThresholdFailures = getThresholdFailures(overallCoverage, COMPONENTS_GLOBAL_THRESHOLDS) +const moduleCoverageRows = [...moduleCoverageMap.entries()] + .map(([moduleName, stats]) => ({ + moduleName, + stats, + thresholds: getComponentModuleThreshold(moduleName), + })) + .map(row => ({ + ...row, + failures: row.thresholds ? getThresholdFailures(row.stats, row.thresholds) : [], + })) +const moduleThresholdFailures = moduleCoverageRows + .filter(row => row.failures.length > 0) + .flatMap(row => row.failures.map(failure => ({ + moduleName: row.moduleName, + ...failure, + }))) +const hasRelevantTestChanges = changedTestFiles.length > 0 +const missingTestTouch = !hasRelevantTestChanges + +appendSummary(buildSummary({ + overallCoverage, + overallThresholdFailures, + moduleCoverageRows, + moduleThresholdFailures, + diffRows, + diffFailures, + diffCoveragePct, + changedSourceFiles, + changedTestFiles, + missingTestTouch, +})) + +if (diffFailures.length > 0 && process.env.CI) { + for (const failure of diffFailures.slice(0, 20)) { + const firstLine = failure.uncoveredLines[0] ?? 1 + console.log(`::error file=${failure.file},line=${firstLine}::Uncovered changed lines: ${formatLineRanges(failure.uncoveredLines)}`) + } +} + +if ( + overallThresholdFailures.length > 0 + || moduleThresholdFailures.length > 0 + || diffFailures.length > 0 + || (STRICT_TEST_FILE_TOUCH && missingTestTouch) +) { + process.exit(1) +} + +function buildSummary({ + overallCoverage, + overallThresholdFailures, + moduleCoverageRows, + moduleThresholdFailures, + diffRows, + diffFailures, + diffCoveragePct, + changedSourceFiles, + changedTestFiles, + missingTestTouch, +}) { + const lines = [ + '### app/components Diff Coverage', + '', + `Compared \`${baseSha.slice(0, 12)}\` -> \`${headSha.slice(0, 12)}\``, + '', + `Excluded modules: \`${EXCLUDED_MODULES_LABEL}\``, + `Excluded file kinds: \`${COMPONENT_COVERAGE_EXCLUDE_LABEL}\``, + '', + '| Check | Result | Details |', + '|---|---:|---|', + `| Overall tracked lines | ${formatPercent(overallCoverage.lines)} | ${overallCoverage.lines.covered}/${overallCoverage.lines.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.lines}% |`, + `| Overall tracked statements | ${formatPercent(overallCoverage.statements)} | ${overallCoverage.statements.covered}/${overallCoverage.statements.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.statements}% |`, + `| Overall tracked functions | ${formatPercent(overallCoverage.functions)} | ${overallCoverage.functions.covered}/${overallCoverage.functions.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.functions}% |`, + `| Overall tracked branches | ${formatPercent(overallCoverage.branches)} | ${overallCoverage.branches.covered}/${overallCoverage.branches.total}; threshold ${COMPONENTS_GLOBAL_THRESHOLDS.branches}% |`, + `| Changed executable lines | ${formatPercent({ covered: diffTotals.covered, total: diffTotals.total })} | ${diffTotals.covered}/${diffTotals.total} |`, + '', + ] + + if (overallThresholdFailures.length > 0) { + lines.push('Overall thresholds failed:') + for (const failure of overallThresholdFailures) + lines.push(`- ${failure.metric}: ${failure.actual.toFixed(2)}% < ${failure.expected}%`) + lines.push('') + } + + if (moduleThresholdFailures.length > 0) { + lines.push('Module thresholds failed:') + for (const failure of moduleThresholdFailures) + lines.push(`- ${failure.moduleName} ${failure.metric}: ${failure.actual.toFixed(2)}% < ${failure.expected}%`) + lines.push('') + } + + const moduleRows = moduleCoverageRows + .map(({ moduleName, stats, thresholds, failures }) => ({ + moduleName, + lines: percentage(stats.lines.covered, stats.lines.total), + statements: percentage(stats.statements.covered, stats.statements.total), + functions: percentage(stats.functions.covered, stats.functions.total), + branches: percentage(stats.branches.covered, stats.branches.total), + thresholds, + failures, + })) + .sort((a, b) => { + if (a.failures.length !== b.failures.length) + return b.failures.length - a.failures.length + + return a.lines - b.lines || a.moduleName.localeCompare(b.moduleName) + }) + + lines.push('
Module coverage') + lines.push('') + lines.push('| Module | Lines | Statements | Functions | Branches | Thresholds | Status |') + lines.push('|---|---:|---:|---:|---:|---|---|') + for (const row of moduleRows) { + const thresholdLabel = row.thresholds + ? `L${row.thresholds.lines}/S${row.thresholds.statements}/F${row.thresholds.functions}/B${row.thresholds.branches}` + : 'n/a' + const status = row.thresholds ? (row.failures.length > 0 ? 'fail' : 'pass') : 'info' + lines.push(`| ${row.moduleName} | ${row.lines.toFixed(2)}% | ${row.statements.toFixed(2)}% | ${row.functions.toFixed(2)}% | ${row.branches.toFixed(2)}% | ${thresholdLabel} | ${status} |`) + } + lines.push('
') + lines.push('') + + const changedRows = diffRows + .filter(row => row.total > 0) + .sort((a, b) => { + const aPct = percentage(rowCovered(a), rowTotal(a)) + const bPct = percentage(rowCovered(b), rowTotal(b)) + return aPct - bPct || a.file.localeCompare(b.file) + }) + + lines.push('
Changed file coverage') + lines.push('') + lines.push('| File | Module | Changed executable lines | Coverage | Uncovered lines |') + lines.push('|---|---|---:|---:|---|') + for (const row of changedRows) { + const rowPct = percentage(row.covered, row.total) + lines.push(`| ${row.file.replace('web/', '')} | ${row.moduleName} | ${row.total} | ${rowPct.toFixed(2)}% | ${formatLineRanges(row.uncoveredLines)} |`) + } + lines.push('
') + lines.push('') + + if (missingTestTouch) { + lines.push(`Warning: tracked source files changed under \`web/app/components/\`, but no test files changed under \`web/app/components/**\` or \`web/__tests__/\`.`) + if (STRICT_TEST_FILE_TOUCH) + lines.push('`STRICT_COMPONENT_TEST_TOUCH=true` is enabled, so this warning fails the check.') + lines.push('') + } + else { + lines.push(`Relevant test files changed: ${changedTestFiles.length}`) + lines.push('') + } + + if (diffFailures.length > 0) { + lines.push('Uncovered changed lines:') + for (const row of diffFailures) { + lines.push(`- ${row.file.replace('web/', '')}: ${formatLineRanges(row.uncoveredLines)}`) + } + lines.push('') + } + + lines.push(`Changed source files checked: ${changedSourceFiles.length}`) + lines.push(`Changed executable line coverage: ${diffCoveragePct.toFixed(2)}%`) + + return lines +} + +function buildSkipSummary(changedExcludedSourceFiles) { + const lines = [ + '### app/components Diff Coverage', + '', + `Excluded modules: \`${EXCLUDED_MODULES_LABEL}\``, + `Excluded file kinds: \`${COMPONENT_COVERAGE_EXCLUDE_LABEL}\``, + '', + ] + + if (changedExcludedSourceFiles.length > 0) { + lines.push('Only excluded component modules or type-only files changed, so diff coverage check was skipped.') + lines.push(`Skipped files: ${changedExcludedSourceFiles.length}`) + } + else { + lines.push('No source changes under tracked `web/app/components/`. Diff coverage check skipped.') + } + + return lines +} + +function getChangedFiles(base, head) { + const output = execGit(['diff', '--name-only', '--diff-filter=ACMR', `${base}...${head}`, '--', 'web/app/components', 'web/__tests__']) + return output + .split('\n') + .map(line => line.trim()) + .filter(Boolean) +} + +function getChangedLineMap(base, head) { + const diff = execGit(['diff', '--unified=0', '--no-color', '--diff-filter=ACMR', `${base}...${head}`, '--', 'web/app/components']) + const lineMap = new Map() + let currentFile = null + + for (const line of diff.split('\n')) { + if (line.startsWith('+++ b/')) { + currentFile = line.slice(6).trim() + continue + } + + if (!currentFile || !isTrackedComponentSourceFile(currentFile)) + continue + + const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/) + if (!match) + continue + + const start = Number(match[1]) + const count = match[2] ? Number(match[2]) : 1 + if (count === 0) + continue + + const linesForFile = lineMap.get(currentFile) ?? new Set() + for (let offset = 0; offset < count; offset += 1) + linesForFile.add(start + offset) + lineMap.set(currentFile, linesForFile) + } + + return lineMap +} + +function isAnyComponentSourceFile(filePath) { + return filePath.startsWith(APP_COMPONENTS_PREFIX) + && /\.(?:ts|tsx)$/.test(filePath) + && !isTestLikePath(filePath) +} + +function isTrackedComponentSourceFile(filePath) { + return isAnyComponentSourceFile(filePath) + && !isExcludedComponentSourceFile(filePath) +} + +function isExcludedComponentSourceFile(filePath) { + return isAnyComponentSourceFile(filePath) + && ( + EXCLUDED_COMPONENT_MODULES.has(getModuleName(filePath)) + || excludedComponentCoverageFiles.has(filePath) + ) +} + +function isRelevantTestFile(filePath) { + return filePath.startsWith(SHARED_TEST_PREFIX) + || (filePath.startsWith(APP_COMPONENTS_PREFIX) && isTestLikePath(filePath) && !isExcludedComponentTestFile(filePath)) +} + +function isExcludedComponentTestFile(filePath) { + if (!filePath.startsWith(APP_COMPONENTS_PREFIX)) + return false + + return EXCLUDED_COMPONENT_MODULES.has(getModuleName(filePath)) +} + +function isTestLikePath(filePath) { + return /(?:^|\/)__tests__\//.test(filePath) + || /(?:^|\/)__mocks__\//.test(filePath) + || /\.(?:spec|test)\.(?:ts|tsx)$/.test(filePath) + || /\.stories\.(?:ts|tsx)$/.test(filePath) + || /\.d\.ts$/.test(filePath) +} + +function getCoverageStats(entry) { + const lineHits = getLineHits(entry) + const statementHits = Object.values(entry.s ?? {}) + const functionHits = Object.values(entry.f ?? {}) + const branchHits = Object.values(entry.b ?? {}).flat() + + return { + lines: { + covered: Object.values(lineHits).filter(count => count > 0).length, + total: Object.keys(lineHits).length, + }, + statements: { + covered: statementHits.filter(count => count > 0).length, + total: statementHits.length, + }, + functions: { + covered: functionHits.filter(count => count > 0).length, + total: functionHits.length, + }, + branches: { + covered: branchHits.filter(count => count > 0).length, + total: branchHits.length, + }, + } +} + +function getLineHits(entry) { + if (entry.l && Object.keys(entry.l).length > 0) + return entry.l + + const lineHits = {} + for (const [statementId, statement] of Object.entries(entry.statementMap ?? {})) { + const line = statement?.start?.line + if (!line) + continue + + const hits = entry.s?.[statementId] ?? 0 + const previous = lineHits[line] + lineHits[line] = previous === undefined ? hits : Math.max(previous, hits) + } + + return lineHits +} + +function sumCoverageStats(rows) { + const total = createEmptyCoverageStats() + for (const row of rows) + addCoverageStats(total, row) + return total +} + +function mergeCoverageStats(map, moduleName, stats) { + const existing = map.get(moduleName) ?? createEmptyCoverageStats() + addCoverageStats(existing, stats) + map.set(moduleName, existing) +} + +function addCoverageStats(target, source) { + for (const metric of ['lines', 'statements', 'functions', 'branches']) { + target[metric].covered += source[metric].covered + target[metric].total += source[metric].total + } +} + +function createEmptyCoverageStats() { + return { + lines: { covered: 0, total: 0 }, + statements: { covered: 0, total: 0 }, + functions: { covered: 0, total: 0 }, + branches: { covered: 0, total: 0 }, + } +} + +function getThresholdFailures(stats, thresholds) { + const failures = [] + for (const metric of ['lines', 'statements', 'functions', 'branches']) { + const actual = percentage(stats[metric].covered, stats[metric].total) + const expected = thresholds[metric] + if (actual < expected) { + failures.push({ + metric, + actual, + expected, + }) + } + } + return failures +} + +function getModuleName(filePath) { + const relativePath = filePath.slice(APP_COMPONENTS_PREFIX.length) + if (!relativePath) + return '(root)' + + const segments = relativePath.split('/') + return segments.length === 1 ? '(root)' : segments[0] +} + +function normalizeToRepoRelative(filePath) { + if (!filePath) + return '' + + if (filePath.startsWith(APP_COMPONENTS_PREFIX) || filePath.startsWith(SHARED_TEST_PREFIX)) + return filePath + + if (filePath.startsWith(APP_COMPONENTS_COVERAGE_PREFIX)) + return `web/${filePath}` + + const absolutePath = path.isAbsolute(filePath) + ? filePath + : path.resolve(webRoot, filePath) + + return path.relative(repoRoot, absolutePath).split(path.sep).join('/') +} + +function formatLineRanges(lines) { + if (!lines || lines.length === 0) + return '' + + const ranges = [] + let start = lines[0] + let end = lines[0] + + for (let index = 1; index < lines.length; index += 1) { + const current = lines[index] + if (current === end + 1) { + end = current + continue + } + + ranges.push(start === end ? `${start}` : `${start}-${end}`) + start = current + end = current + } + + ranges.push(start === end ? `${start}` : `${start}-${end}`) + return ranges.join(', ') +} + +function percentage(covered, total) { + if (total === 0) + return 100 + return (covered / total) * 100 +} + +function formatPercent(metric) { + return `${percentage(metric.covered, metric.total).toFixed(2)}%` +} + +function appendSummary(lines) { + const content = `${lines.join('\n')}\n` + if (process.env.GITHUB_STEP_SUMMARY) + fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, content) + console.log(content) +} + +function execGit(args) { + return execFileSync('git', args, { + cwd: repoRoot, + encoding: 'utf8', + }) +} + +function repoRootFromCwd() { + return execFileSync('git', ['rev-parse', '--show-toplevel'], { + cwd: process.cwd(), + encoding: 'utf8', + }).trim() +} + +function rowCovered(row) { + return row.covered +} + +function rowTotal(row) { + return row.total +} diff --git a/web/scripts/component-coverage-filters.mjs b/web/scripts/component-coverage-filters.mjs new file mode 100644 index 0000000000..e33c843cb4 --- /dev/null +++ b/web/scripts/component-coverage-filters.mjs @@ -0,0 +1,316 @@ +import fs from 'node:fs' +import path from 'node:path' +import tsParser from '@typescript-eslint/parser' + +const TS_TSX_FILE_PATTERN = /\.(?:ts|tsx)$/ +const TYPE_COVERAGE_EXCLUDE_BASENAMES = new Set([ + 'type', + 'types', + 'declarations', +]) +const GENERATED_FILE_COMMENT_PATTERNS = [ + /@generated/i, + /\bauto-?generated\b/i, + /\bgenerated by\b/i, + /\bgenerate by\b/i, + /\bdo not edit\b/i, + /\bdon not edit\b/i, +] +const PARSER_OPTIONS = { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true }, +} + +const collectedExcludedFilesCache = new Map() + +export const COMPONENT_COVERAGE_EXCLUDE_LABEL = 'type-only files, pure barrel files, generated files, pure static files' + +export function isTypeCoverageExcludedComponentFile(filePath) { + return TYPE_COVERAGE_EXCLUDE_BASENAMES.has(getPathBaseNameWithoutExtension(filePath)) +} + +export function getComponentCoverageExclusionReasons(filePath, sourceCode) { + if (!isEligibleComponentSourceFilePath(filePath)) + return [] + + const reasons = [] + if (isTypeCoverageExcludedComponentFile(filePath)) + reasons.push('type-only') + + if (typeof sourceCode !== 'string' || sourceCode.length === 0) + return reasons + + if (isGeneratedComponentFile(sourceCode)) + reasons.push('generated') + + const ast = parseComponentFile(sourceCode) + if (!ast) + return reasons + + if (isPureBarrelComponentFile(ast)) + reasons.push('pure-barrel') + else if (isPureStaticComponentFile(ast)) + reasons.push('pure-static') + + return reasons +} + +export function collectComponentCoverageExcludedFiles(rootDir, options = {}) { + const normalizedRootDir = path.resolve(rootDir) + const pathPrefix = normalizePathPrefix(options.pathPrefix ?? '') + const cacheKey = `${normalizedRootDir}::${pathPrefix}` + const cached = collectedExcludedFilesCache.get(cacheKey) + if (cached) + return cached + + const files = [] + walkComponentFiles(normalizedRootDir, (absolutePath) => { + const relativePath = path.relative(normalizedRootDir, absolutePath).split(path.sep).join('/') + const prefixedPath = pathPrefix ? `${pathPrefix}/${relativePath}` : relativePath + const sourceCode = fs.readFileSync(absolutePath, 'utf8') + if (getComponentCoverageExclusionReasons(prefixedPath, sourceCode).length > 0) + files.push(prefixedPath) + }) + + files.sort((a, b) => a.localeCompare(b)) + collectedExcludedFilesCache.set(cacheKey, files) + return files +} + +function normalizePathPrefix(pathPrefix) { + return pathPrefix.replace(/\\/g, '/').replace(/\/$/, '') +} + +function walkComponentFiles(currentDir, onFile) { + if (!fs.existsSync(currentDir)) + return + + const entries = fs.readdirSync(currentDir, { withFileTypes: true }) + for (const entry of entries) { + const entryPath = path.join(currentDir, entry.name) + if (entry.isDirectory()) { + if (entry.name === '__tests__' || entry.name === '__mocks__') + continue + walkComponentFiles(entryPath, onFile) + continue + } + + if (!isEligibleComponentSourceFilePath(entry.name)) + continue + + onFile(entryPath) + } +} + +function isEligibleComponentSourceFilePath(filePath) { + return TS_TSX_FILE_PATTERN.test(filePath) + && !isTestLikePath(filePath) +} + +function isTestLikePath(filePath) { + return /(?:^|\/)__tests__\//.test(filePath) + || /(?:^|\/)__mocks__\//.test(filePath) + || /\.(?:spec|test)\.(?:ts|tsx)$/.test(filePath) + || /\.stories\.(?:ts|tsx)$/.test(filePath) + || /\.d\.ts$/.test(filePath) +} + +function getPathBaseNameWithoutExtension(filePath) { + if (!filePath) + return '' + + const normalizedPath = filePath.replace(/\\/g, '/') + const fileName = normalizedPath.split('/').pop() ?? '' + return fileName.replace(TS_TSX_FILE_PATTERN, '') +} + +function isGeneratedComponentFile(sourceCode) { + const leadingText = sourceCode.split('\n').slice(0, 5).join('\n') + return GENERATED_FILE_COMMENT_PATTERNS.some(pattern => pattern.test(leadingText)) +} + +function parseComponentFile(sourceCode) { + try { + return tsParser.parse(sourceCode, PARSER_OPTIONS) + } + catch { + return null + } +} + +function isPureBarrelComponentFile(ast) { + let hasRuntimeReExports = false + + for (const statement of ast.body) { + if (statement.type === 'ExportAllDeclaration') { + hasRuntimeReExports = true + continue + } + + if (statement.type === 'ExportNamedDeclaration' && statement.source) { + hasRuntimeReExports = hasRuntimeReExports || statement.exportKind !== 'type' + continue + } + + if (statement.type === 'TSInterfaceDeclaration' || statement.type === 'TSTypeAliasDeclaration') + continue + + return false + } + + return hasRuntimeReExports +} + +function isPureStaticComponentFile(ast) { + const importedStaticBindings = collectImportedStaticBindings(ast.body) + const staticBindings = new Set() + let hasRuntimeValue = false + + for (const statement of ast.body) { + if (statement.type === 'ImportDeclaration') + continue + + if (statement.type === 'TSInterfaceDeclaration' || statement.type === 'TSTypeAliasDeclaration') + continue + + if (statement.type === 'ExportAllDeclaration') + return false + + if (statement.type === 'ExportNamedDeclaration' && statement.source) + return false + + if (statement.type === 'ExportDefaultDeclaration') { + if (!isStaticExpression(statement.declaration, staticBindings, importedStaticBindings)) + return false + hasRuntimeValue = true + continue + } + + if (statement.type === 'ExportNamedDeclaration' && statement.declaration) { + if (!handleStaticDeclaration(statement.declaration, staticBindings, importedStaticBindings)) + return false + hasRuntimeValue = true + continue + } + + if (statement.type === 'ExportNamedDeclaration' && statement.specifiers.length > 0) { + const allStaticSpecifiers = statement.specifiers.every((specifier) => { + if (specifier.type !== 'ExportSpecifier' || specifier.exportKind === 'type') + return false + return specifier.local.type === 'Identifier' && staticBindings.has(specifier.local.name) + }) + if (!allStaticSpecifiers) + return false + hasRuntimeValue = true + continue + } + + if (!handleStaticDeclaration(statement, staticBindings, importedStaticBindings)) + return false + hasRuntimeValue = true + } + + return hasRuntimeValue +} + +function handleStaticDeclaration(statement, staticBindings, importedStaticBindings) { + if (statement.type !== 'VariableDeclaration' || statement.kind !== 'const') + return false + + for (const declarator of statement.declarations) { + if (declarator.id.type !== 'Identifier' || !declarator.init) + return false + + if (!isStaticExpression(declarator.init, staticBindings, importedStaticBindings)) + return false + + staticBindings.add(declarator.id.name) + } + + return true +} + +function collectImportedStaticBindings(statements) { + const importedBindings = new Set() + + for (const statement of statements) { + if (statement.type !== 'ImportDeclaration') + continue + + const importSource = String(statement.source.value ?? '') + const isTypeLikeSource = isTypeCoverageExcludedComponentFile(importSource) + const importIsStatic = statement.importKind === 'type' || isTypeLikeSource + if (!importIsStatic) + continue + + for (const specifier of statement.specifiers) { + if (specifier.local?.type === 'Identifier') + importedBindings.add(specifier.local.name) + } + } + + return importedBindings +} + +function isStaticExpression(node, staticBindings, importedStaticBindings) { + switch (node.type) { + case 'Literal': + return true + case 'Identifier': + return staticBindings.has(node.name) || importedStaticBindings.has(node.name) + case 'TemplateLiteral': + return node.expressions.every(expression => isStaticExpression(expression, staticBindings, importedStaticBindings)) + case 'ArrayExpression': + return node.elements.every(element => !element || isStaticExpression(element, staticBindings, importedStaticBindings)) + case 'ObjectExpression': + return node.properties.every((property) => { + if (property.type === 'SpreadElement') + return isStaticExpression(property.argument, staticBindings, importedStaticBindings) + + if (property.type !== 'Property' || property.method) + return false + + if (property.computed && !isStaticExpression(property.key, staticBindings, importedStaticBindings)) + return false + + if (property.shorthand) + return property.value.type === 'Identifier' && staticBindings.has(property.value.name) + + return isStaticExpression(property.value, staticBindings, importedStaticBindings) + }) + case 'UnaryExpression': + return isStaticExpression(node.argument, staticBindings, importedStaticBindings) + case 'BinaryExpression': + case 'LogicalExpression': + return isStaticExpression(node.left, staticBindings, importedStaticBindings) + && isStaticExpression(node.right, staticBindings, importedStaticBindings) + case 'ConditionalExpression': + return isStaticExpression(node.test, staticBindings, importedStaticBindings) + && isStaticExpression(node.consequent, staticBindings, importedStaticBindings) + && isStaticExpression(node.alternate, staticBindings, importedStaticBindings) + case 'MemberExpression': + return isStaticMemberExpression(node, staticBindings, importedStaticBindings) + case 'ChainExpression': + return isStaticExpression(node.expression, staticBindings, importedStaticBindings) + case 'TSAsExpression': + case 'TSSatisfiesExpression': + case 'TSTypeAssertion': + case 'TSNonNullExpression': + return isStaticExpression(node.expression, staticBindings, importedStaticBindings) + case 'ParenthesizedExpression': + return isStaticExpression(node.expression, staticBindings, importedStaticBindings) + default: + return false + } +} + +function isStaticMemberExpression(node, staticBindings, importedStaticBindings) { + if (!isStaticExpression(node.object, staticBindings, importedStaticBindings)) + return false + + if (!node.computed) + return node.property.type === 'Identifier' + + return isStaticExpression(node.property, staticBindings, importedStaticBindings) +} diff --git a/web/scripts/components-coverage-thresholds.mjs b/web/scripts/components-coverage-thresholds.mjs new file mode 100644 index 0000000000..d61a6ad814 --- /dev/null +++ b/web/scripts/components-coverage-thresholds.mjs @@ -0,0 +1,128 @@ +// Floors were set from the app/components baseline captured on 2026-03-13, +// with a small buffer to avoid CI noise on existing code. +export const EXCLUDED_COMPONENT_MODULES = new Set([ + 'devtools', + 'provider', +]) + +export const COMPONENTS_GLOBAL_THRESHOLDS = { + lines: 58, + statements: 58, + functions: 58, + branches: 54, +} + +export const COMPONENT_MODULE_THRESHOLDS = { + 'app': { + lines: 45, + statements: 45, + functions: 50, + branches: 35, + }, + 'app-sidebar': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'apps': { + lines: 90, + statements: 90, + functions: 85, + branches: 80, + }, + 'base': { + lines: 95, + statements: 95, + functions: 90, + branches: 95, + }, + 'billing': { + lines: 95, + statements: 95, + functions: 95, + branches: 95, + }, + 'custom': { + lines: 70, + statements: 70, + functions: 70, + branches: 80, + }, + 'datasets': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'develop': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'explore': { + lines: 95, + statements: 95, + functions: 95, + branches: 85, + }, + 'goto-anything': { + lines: 90, + statements: 90, + functions: 90, + branches: 90, + }, + 'header': { + lines: 95, + statements: 95, + functions: 95, + branches: 95, + }, + 'plugins': { + lines: 90, + statements: 90, + functions: 90, + branches: 85, + }, + 'rag-pipeline': { + lines: 95, + statements: 95, + functions: 95, + branches: 90, + }, + 'share': { + lines: 15, + statements: 15, + functions: 20, + branches: 20, + }, + 'signin': { + lines: 95, + statements: 95, + functions: 95, + branches: 95, + }, + 'tools': { + lines: 95, + statements: 95, + functions: 90, + branches: 90, + }, + 'workflow': { + lines: 15, + statements: 15, + functions: 10, + branches: 10, + }, + 'workflow-app': { + lines: 20, + statements: 20, + functions: 25, + branches: 15, + }, +} + +export function getComponentModuleThreshold(moduleName) { + return COMPONENT_MODULE_THRESHOLDS[moduleName] ?? null +} diff --git a/web/vite.config.ts b/web/vite.config.ts index d0c7e947a2..3a61264e3c 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -8,15 +8,24 @@ import { defineConfig } from 'vite' import Inspect from 'vite-plugin-inspect' import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr' +import { collectComponentCoverageExcludedFiles } from './scripts/component-coverage-filters.mjs' +import { EXCLUDED_COMPONENT_MODULES } from './scripts/components-coverage-thresholds.mjs' const projectRoot = path.dirname(fileURLToPath(import.meta.url)) const isCI = !!process.env.CI +const coverageScope = process.env.VITEST_COVERAGE_SCOPE const browserInitializerInjectTarget = path.resolve(projectRoot, 'app/components/browser-initializer.tsx') +const excludedAppComponentsCoveragePaths = [...EXCLUDED_COMPONENT_MODULES] + .map(moduleName => `app/components/${moduleName}/**`) export default defineConfig(({ mode }) => { const isTest = mode === 'test' const isStorybook = process.env.STORYBOOK === 'true' || process.argv.some(arg => arg.toLowerCase().includes('storybook')) + const isAppComponentsCoverage = coverageScope === 'app-components' + const excludedComponentCoverageFiles = isAppComponentsCoverage + ? collectComponentCoverageExcludedFiles(path.join(projectRoot, 'app/components'), { pathPrefix: 'app/components' }) + : [] return { plugins: isTest @@ -82,6 +91,21 @@ export default defineConfig(({ mode }) => { coverage: { provider: 'v8', reporter: isCI ? ['json', 'json-summary'] : ['text', 'json', 'json-summary'], + ...(isAppComponentsCoverage + ? { + include: ['app/components/**/*.{ts,tsx}'], + exclude: [ + 'app/components/**/*.d.ts', + 'app/components/**/*.spec.{ts,tsx}', + 'app/components/**/*.test.{ts,tsx}', + 'app/components/**/__tests__/**', + 'app/components/**/__mocks__/**', + 'app/components/**/*.stories.{ts,tsx}', + ...excludedComponentCoverageFiles, + ...excludedAppComponentsCoveragePaths, + ], + } + : {}), }, }, } From f38e8cca521093f2ae1225c6161f8261a51ee8c2 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Fri, 13 Mar 2026 17:32:39 +0900 Subject: [PATCH 07/37] test: [Refactor/Chore] use Testcontainers to do sql test #32454 (#32460) --- .../models/test_app_model_config.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 api/tests/test_containers_integration_tests/models/test_app_model_config.py diff --git a/api/tests/test_containers_integration_tests/models/test_app_model_config.py b/api/tests/test_containers_integration_tests/models/test_app_model_config.py new file mode 100644 index 0000000000..e8b36097e1 --- /dev/null +++ b/api/tests/test_containers_integration_tests/models/test_app_model_config.py @@ -0,0 +1,32 @@ +""" +Integration tests for AppModelConfig using testcontainers. + +These tests validate database-backed model behavior without mocking SQLAlchemy queries. +""" + +from uuid import uuid4 + +from sqlalchemy.orm import Session + +from models.model import AppModelConfig + + +class TestAppModelConfig: + """Integration tests for AppModelConfig.""" + + def test_annotation_reply_dict_disabled_without_setting(self, db_session_with_containers: Session) -> None: + """Return disabled annotation reply dict when no AppAnnotationSetting exists.""" + # Arrange + config = AppModelConfig(app_id=str(uuid4())) + db_session_with_containers.add(config) + db_session_with_containers.commit() + + # Act + result = config.annotation_reply_dict + + # Assert + assert result == {"enabled": False} + + # Cleanup + db_session_with_containers.delete(config) + db_session_with_containers.commit() From 20e91990bfbb0b665bd128cd8e0ca8d68e554726 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 17:33:33 +0900 Subject: [PATCH 08/37] chore(deps): bump orjson from 3.11.4 to 3.11.6 in /api (#33380) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 64 ++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index 518668711d..1d03d5a360 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -4461,40 +4461,40 @@ wheels = [ [[package]] name = "orjson" -version = "3.11.4" +version = "3.11.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/fe/ed708782d6709cc60eb4c2d8a361a440661f74134675c72990f2c48c785f/orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d", size = 5945188, upload-time = "2025-10-24T15:50:38.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/a3/4e09c61a5f0c521cba0bb433639610ae037437669f1a4cbc93799e731d78/orjson-3.11.6.tar.gz", hash = "sha256:0a54c72259f35299fd033042367df781c2f66d10252955ca1efb7db309b954cb", size = 6175856, upload-time = "2026-01-29T15:13:07.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/1d/1ea6005fffb56715fd48f632611e163d1604e8316a5bad2288bee9a1c9eb/orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e59d23cd93ada23ec59a96f215139753fbfe3a4d989549bcb390f8c00370b39", size = 243498, upload-time = "2025-10-24T15:48:48.101Z" }, - { url = "https://files.pythonhosted.org/packages/37/d7/ffed10c7da677f2a9da307d491b9eb1d0125b0307019c4ad3d665fd31f4f/orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5c3aedecfc1beb988c27c79d52ebefab93b6c3921dbec361167e6559aba2d36d", size = 128961, upload-time = "2025-10-24T15:48:49.571Z" }, - { url = "https://files.pythonhosted.org/packages/a2/96/3e4d10a18866d1368f73c8c44b7fe37cc8a15c32f2a7620be3877d4c55a3/orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9e5301f1c2caa2a9a4a303480d79c9ad73560b2e7761de742ab39fe59d9175", size = 130321, upload-time = "2025-10-24T15:48:50.713Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1f/465f66e93f434f968dd74d5b623eb62c657bdba2332f5a8be9f118bb74c7/orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8873812c164a90a79f65368f8f96817e59e35d0cc02786a5356f0e2abed78040", size = 129207, upload-time = "2025-10-24T15:48:52.193Z" }, - { url = "https://files.pythonhosted.org/packages/28/43/d1e94837543321c119dff277ae8e348562fe8c0fafbb648ef7cb0c67e521/orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d7feb0741ebb15204e748f26c9638e6665a5fa93c37a2c73d64f1669b0ddc63", size = 136323, upload-time = "2025-10-24T15:48:54.806Z" }, - { url = "https://files.pythonhosted.org/packages/bf/04/93303776c8890e422a5847dd012b4853cdd88206b8bbd3edc292c90102d1/orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ee5487fefee21e6910da4c2ee9eef005bee568a0879834df86f888d2ffbdd9", size = 137440, upload-time = "2025-10-24T15:48:56.326Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ef/75519d039e5ae6b0f34d0336854d55544ba903e21bf56c83adc51cd8bf82/orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d40d46f348c0321df01507f92b95a377240c4ec31985225a6668f10e2676f9a", size = 136680, upload-time = "2025-10-24T15:48:57.476Z" }, - { url = "https://files.pythonhosted.org/packages/b5/18/bf8581eaae0b941b44efe14fee7b7862c3382fbc9a0842132cfc7cf5ecf4/orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95713e5fc8af84d8edc75b785d2386f653b63d62b16d681687746734b4dfc0be", size = 136160, upload-time = "2025-10-24T15:48:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/c4/35/a6d582766d351f87fc0a22ad740a641b0a8e6fc47515e8614d2e4790ae10/orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad73ede24f9083614d6c4ca9a85fe70e33be7bf047ec586ee2363bc7418fe4d7", size = 140318, upload-time = "2025-10-24T15:49:00.834Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/5a4801803ab2e2e2d703bce1a56540d9f99a9143fbec7bf63d225044fef8/orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:842289889de515421f3f224ef9c1f1efb199a32d76d8d2ca2706fa8afe749549", size = 406330, upload-time = "2025-10-24T15:49:02.327Z" }, - { url = "https://files.pythonhosted.org/packages/80/55/a8f682f64833e3a649f620eafefee175cbfeb9854fc5b710b90c3bca45df/orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3b2427ed5791619851c52a1261b45c233930977e7de8cf36de05636c708fa905", size = 149580, upload-time = "2025-10-24T15:49:03.517Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e4/c132fa0c67afbb3eb88274fa98df9ac1f631a675e7877037c611805a4413/orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c36e524af1d29982e9b190573677ea02781456b2e537d5840e4538a5ec41907", size = 139846, upload-time = "2025-10-24T15:49:04.761Z" }, - { url = "https://files.pythonhosted.org/packages/54/06/dc3491489efd651fef99c5908e13951abd1aead1257c67f16135f95ce209/orjson-3.11.4-cp311-cp311-win32.whl", hash = "sha256:87255b88756eab4a68ec61837ca754e5d10fa8bc47dc57f75cedfeaec358d54c", size = 135781, upload-time = "2025-10-24T15:49:05.969Z" }, - { url = "https://files.pythonhosted.org/packages/79/b7/5e5e8d77bd4ea02a6ac54c42c818afb01dd31961be8a574eb79f1d2cfb1e/orjson-3.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:e2d5d5d798aba9a0e1fede8d853fa899ce2cb930ec0857365f700dffc2c7af6a", size = 131391, upload-time = "2025-10-24T15:49:07.355Z" }, - { url = "https://files.pythonhosted.org/packages/0f/dc/9484127cc1aa213be398ed735f5f270eedcb0c0977303a6f6ddc46b60204/orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045", size = 126252, upload-time = "2025-10-24T15:49:08.869Z" }, - { url = "https://files.pythonhosted.org/packages/63/51/6b556192a04595b93e277a9ff71cd0cc06c21a7df98bcce5963fa0f5e36f/orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50", size = 243571, upload-time = "2025-10-24T15:49:10.008Z" }, - { url = "https://files.pythonhosted.org/packages/1c/2c/2602392ddf2601d538ff11848b98621cd465d1a1ceb9db9e8043181f2f7b/orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853", size = 128891, upload-time = "2025-10-24T15:49:11.297Z" }, - { url = "https://files.pythonhosted.org/packages/4e/47/bf85dcf95f7a3a12bf223394a4f849430acd82633848d52def09fa3f46ad/orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938", size = 130137, upload-time = "2025-10-24T15:49:12.544Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4d/a0cb31007f3ab6f1fd2a1b17057c7c349bc2baf8921a85c0180cc7be8011/orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415", size = 129152, upload-time = "2025-10-24T15:49:13.754Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ef/2811def7ce3d8576b19e3929fff8f8f0d44bc5eb2e0fdecb2e6e6cc6c720/orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44", size = 136834, upload-time = "2025-10-24T15:49:15.307Z" }, - { url = "https://files.pythonhosted.org/packages/00/d4/9aee9e54f1809cec8ed5abd9bc31e8a9631d19460e3b8470145d25140106/orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2", size = 137519, upload-time = "2025-10-24T15:49:16.557Z" }, - { url = "https://files.pythonhosted.org/packages/db/ea/67bfdb5465d5679e8ae8d68c11753aaf4f47e3e7264bad66dc2f2249e643/orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708", size = 136749, upload-time = "2025-10-24T15:49:17.796Z" }, - { url = "https://files.pythonhosted.org/packages/01/7e/62517dddcfce6d53a39543cd74d0dccfcbdf53967017c58af68822100272/orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210", size = 136325, upload-time = "2025-10-24T15:49:19.347Z" }, - { url = "https://files.pythonhosted.org/packages/18/ae/40516739f99ab4c7ec3aaa5cc242d341fcb03a45d89edeeaabc5f69cb2cf/orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241", size = 140204, upload-time = "2025-10-24T15:49:20.545Z" }, - { url = "https://files.pythonhosted.org/packages/82/18/ff5734365623a8916e3a4037fcef1cd1782bfc14cf0992afe7940c5320bf/orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b", size = 406242, upload-time = "2025-10-24T15:49:21.884Z" }, - { url = "https://files.pythonhosted.org/packages/e1/43/96436041f0a0c8c8deca6a05ebeaf529bf1de04839f93ac5e7c479807aec/orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c", size = 150013, upload-time = "2025-10-24T15:49:23.185Z" }, - { url = "https://files.pythonhosted.org/packages/1b/48/78302d98423ed8780479a1e682b9aecb869e8404545d999d34fa486e573e/orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9", size = 139951, upload-time = "2025-10-24T15:49:24.428Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7b/ad613fdcdaa812f075ec0875143c3d37f8654457d2af17703905425981bf/orjson-3.11.4-cp312-cp312-win32.whl", hash = "sha256:b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa", size = 136049, upload-time = "2025-10-24T15:49:25.973Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3c/9cf47c3ff5f39b8350fb21ba65d789b6a1129d4cbb3033ba36c8a9023520/orjson-3.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140", size = 131461, upload-time = "2025-10-24T15:49:27.259Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3b/e2425f61e5825dc5b08c2a5a2b3af387eaaca22a12b9c8c01504f8614c36/orjson-3.11.4-cp312-cp312-win_arm64.whl", hash = "sha256:d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e", size = 126167, upload-time = "2025-10-24T15:49:28.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d6b0a36854179b93ed77839f107c4089d91cccc9f9ba1b752b6e3bac5f34/orjson-3.11.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e259e85a81d76d9665f03d6129e09e4435531870de5961ddcd0bf6e3a7fde7d7", size = 250029, upload-time = "2026-01-29T15:11:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/a3/bb/22902619826641cf3b627c24aab62e2ad6b571bdd1d34733abb0dd57f67a/orjson-3.11.6-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:52263949f41b4a4822c6b1353bcc5ee2f7109d53a3b493501d3369d6d0e7937a", size = 134518, upload-time = "2026-01-29T15:11:37.347Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/7a818da4bba1de711a9653c420749c0ac95ef8f8651cbc1dca551f462fe0/orjson-3.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6439e742fa7834a24698d358a27346bb203bff356ae0402e7f5df8f749c621a8", size = 137917, upload-time = "2026-01-29T15:11:38.511Z" }, + { url = "https://files.pythonhosted.org/packages/59/0f/02846c1cac8e205cb3822dd8aa8f9114acda216f41fd1999ace6b543418d/orjson-3.11.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b81ffd68f084b4e993e3867acb554a049fa7787cc8710bbcc1e26965580d99be", size = 134923, upload-time = "2026-01-29T15:11:39.711Z" }, + { url = "https://files.pythonhosted.org/packages/94/cf/aeaf683001b474bb3c3c757073a4231dfdfe8467fceaefa5bfd40902c99f/orjson-3.11.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5a5468e5e60f7ef6d7f9044b06c8f94a3c56ba528c6e4f7f06ae95164b595ec", size = 140752, upload-time = "2026-01-29T15:11:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fe/dad52d8315a65f084044a0819d74c4c9daf9ebe0681d30f525b0d29a31f0/orjson-3.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72c5005eb45bd2535632d4f3bec7ad392832cfc46b62a3021da3b48a67734b45", size = 144201, upload-time = "2026-01-29T15:11:42.537Z" }, + { url = "https://files.pythonhosted.org/packages/36/bc/ab070dd421565b831801077f1e390c4d4af8bfcecafc110336680a33866b/orjson-3.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b14dd49f3462b014455a28a4d810d3549bf990567653eb43765cd847df09145", size = 142380, upload-time = "2026-01-29T15:11:44.309Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d8/4b581c725c3a308717f28bf45a9fdac210bca08b67e8430143699413ff06/orjson-3.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bb2c1ea30ef302f0f89f9bf3e7f9ab5e2af29dc9f80eb87aa99788e4e2d65", size = 145582, upload-time = "2026-01-29T15:11:45.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a2/09aab99b39f9a7f175ea8fa29adb9933a3d01e7d5d603cdee7f1c40c8da2/orjson-3.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:825e0a85d189533c6bff7e2fc417a28f6fcea53d27125c4551979aecd6c9a197", size = 147270, upload-time = "2026-01-29T15:11:46.782Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2f/5ef8eaf7829dc50da3bf497c7775b21ee88437bc8c41f959aa3504ca6631/orjson-3.11.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b04575417a26530637f6ab4b1f7b4f666eb0433491091da4de38611f97f2fcf3", size = 421222, upload-time = "2026-01-29T15:11:48.106Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b0/dd6b941294c2b5b13da5fdc7e749e58d0c55a5114ab37497155e83050e95/orjson-3.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b83eb2e40e8c4da6d6b340ee6b1d6125f5195eb1b0ebb7eac23c6d9d4f92d224", size = 155562, upload-time = "2026-01-29T15:11:49.408Z" }, + { url = "https://files.pythonhosted.org/packages/8e/09/43924331a847476ae2f9a16bd6d3c9dab301265006212ba0d3d7fd58763a/orjson-3.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1f42da604ee65a6b87eef858c913ce3e5777872b19321d11e6fc6d21de89b64f", size = 147432, upload-time = "2026-01-29T15:11:50.635Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e9/d9865961081816909f6b49d880749dbbd88425afd7c5bbce0549e2290d77/orjson-3.11.6-cp311-cp311-win32.whl", hash = "sha256:5ae45df804f2d344cffb36c43fdf03c82fb6cd247f5faa41e21891b40dfbf733", size = 139623, upload-time = "2026-01-29T15:11:51.82Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f9/6836edb92f76eec1082919101eb1145d2f9c33c8f2c5e6fa399b82a2aaa8/orjson-3.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:f4295948d65ace0a2d8f2c4ccc429668b7eb8af547578ec882e16bf79b0050b2", size = 136647, upload-time = "2026-01-29T15:11:53.454Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0c/4954082eea948c9ae52ee0bcbaa2f99da3216a71bcc314ab129bde22e565/orjson-3.11.6-cp311-cp311-win_arm64.whl", hash = "sha256:314e9c45e0b81b547e3a1cfa3df3e07a815821b3dac9fe8cb75014071d0c16a4", size = 135327, upload-time = "2026-01-29T15:11:56.616Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/759f2879f41910b7e5e0cdbd9cf82a4f017c527fb0e972e9869ca7fe4c8e/orjson-3.11.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6f03f30cd8953f75f2a439070c743c7336d10ee940da918d71c6f3556af3ddcf", size = 249988, upload-time = "2026-01-29T15:11:58.294Z" }, + { url = "https://files.pythonhosted.org/packages/f0/70/54cecb929e6c8b10104fcf580b0cc7dc551aa193e83787dd6f3daba28bb5/orjson-3.11.6-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:af44baae65ef386ad971469a8557a0673bb042b0b9fd4397becd9c2dfaa02588", size = 134445, upload-time = "2026-01-29T15:11:59.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6f/ec0309154457b9ba1ad05f11faa4441f76037152f75e1ac577db3ce7ca96/orjson-3.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c310a48542094e4f7dbb6ac076880994986dda8ca9186a58c3cb70a3514d3231", size = 137708, upload-time = "2026-01-29T15:12:01.488Z" }, + { url = "https://files.pythonhosted.org/packages/20/52/3c71b80840f8bab9cb26417302707b7716b7d25f863f3a541bcfa232fe6e/orjson-3.11.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8dfa7a5d387f15ecad94cb6b2d2d5f4aeea64efd8d526bfc03c9812d01e1cc0", size = 134798, upload-time = "2026-01-29T15:12:02.705Z" }, + { url = "https://files.pythonhosted.org/packages/30/51/b490a43b22ff736282360bd02e6bded455cf31dfc3224e01cd39f919bbd2/orjson-3.11.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba8daee3e999411b50f8b50dbb0a3071dd1845f3f9a1a0a6fa6de86d1689d84d", size = 140839, upload-time = "2026-01-29T15:12:03.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/bc/4bcfe4280c1bc63c5291bb96f98298845b6355da2226d3400e17e7b51e53/orjson-3.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f89d104c974eafd7436d7a5fdbc57f7a1e776789959a2f4f1b2eab5c62a339f4", size = 144080, upload-time = "2026-01-29T15:12:05.151Z" }, + { url = "https://files.pythonhosted.org/packages/01/74/22970f9ead9ab1f1b5f8c227a6c3aa8d71cd2c5acd005868a1d44f2362fa/orjson-3.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e2e2456788ca5ea75616c40da06fc885a7dc0389780e8a41bf7c5389ba257b", size = 142435, upload-time = "2026-01-29T15:12:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/29/34/d564aff85847ab92c82ee43a7a203683566c2fca0723a5f50aebbe759603/orjson-3.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a42efebc45afabb1448001e90458c4020d5c64fbac8a8dc4045b777db76cb5a", size = 145631, upload-time = "2026-01-29T15:12:08.351Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/016957a3890752c4aa2368326ea69fa53cdc1fdae0a94a542b6410dbdf52/orjson-3.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71b7cbef8471324966c3738c90ba38775563ef01b512feb5ad4805682188d1b9", size = 147058, upload-time = "2026-01-29T15:12:10.023Z" }, + { url = "https://files.pythonhosted.org/packages/56/cc/9a899c3972085645b3225569f91a30e221f441e5dc8126e6d060b971c252/orjson-3.11.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f8515e5910f454fe9a8e13c2bb9dc4bae4c1836313e967e72eb8a4ad874f0248", size = 421161, upload-time = "2026-01-29T15:12:11.308Z" }, + { url = "https://files.pythonhosted.org/packages/21/a8/767d3fbd6d9b8fdee76974db40619399355fd49bf91a6dd2c4b6909ccf05/orjson-3.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:300360edf27c8c9bf7047345a94fddf3a8b8922df0ff69d71d854a170cb375cf", size = 155757, upload-time = "2026-01-29T15:12:12.776Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0b/205cd69ac87e2272e13ef3f5f03a3d4657e317e38c1b08aaa2ef97060bbc/orjson-3.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:caaed4dad39e271adfadc106fab634d173b2bb23d9cf7e67bd645f879175ebfc", size = 147446, upload-time = "2026-01-29T15:12:14.166Z" }, + { url = "https://files.pythonhosted.org/packages/de/c5/dd9f22aa9f27c54c7d05cc32f4580c9ac9b6f13811eeb81d6c4c3f50d6b1/orjson-3.11.6-cp312-cp312-win32.whl", hash = "sha256:955368c11808c89793e847830e1b1007503a5923ddadc108547d3b77df761044", size = 139717, upload-time = "2026-01-29T15:12:15.7Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/e62fc50d904486970315a1654b8cfb5832eb46abb18cd5405118e7e1fc79/orjson-3.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:2c68de30131481150073d90a5d227a4a421982f42c025ecdfb66157f9579e06f", size = 136711, upload-time = "2026-01-29T15:12:17.055Z" }, + { url = "https://files.pythonhosted.org/packages/04/3d/b4fefad8bdf91e0fe212eb04975aeb36ea92997269d68857efcc7eb1dda3/orjson-3.11.6-cp312-cp312-win_arm64.whl", hash = "sha256:65dfa096f4e3a5e02834b681f539a87fbe85adc82001383c0db907557f666bfc", size = 135212, upload-time = "2026-01-29T15:12:18.3Z" }, ] [[package]] From 4203647c32f96fb0edaa6e66facbc7f79e6301ca Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 13 Mar 2026 18:18:44 +0800 Subject: [PATCH 09/37] chore: use vite plus (#33407) --- .github/actions/setup-web/action.yml | 34 +- .github/workflows/autofix.yml | 4 +- .github/workflows/style.yml | 8 +- .github/workflows/translate-i18n-claude.yml | 2 - .github/workflows/web-tests.yml | 6 +- web/knip.config.ts | 2 + web/package.json | 14 +- web/pnpm-lock.yaml | 1293 ++++++++++++------- web/vite.config.ts | 4 +- 9 files changed, 865 insertions(+), 502 deletions(-) diff --git a/.github/actions/setup-web/action.yml b/.github/actions/setup-web/action.yml index c57da7cb5f..54702c914a 100644 --- a/.github/actions/setup-web/action.yml +++ b/.github/actions/setup-web/action.yml @@ -1,33 +1,13 @@ name: Setup Web Environment -description: Setup pnpm, Node.js, and install web dependencies. - -inputs: - node-version: - description: Node.js version to use - required: false - default: "22" - install-dependencies: - description: Whether to install web dependencies after setting up Node.js - required: false - default: "true" runs: using: composite steps: - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@b5d848f5a62488f3d3d920f8aa6ac318a60c5f07 # v1 with: - package_json_file: web/package.json - run_install: false - - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: ${{ inputs.node-version }} - cache: pnpm - cache-dependency-path: ./web/pnpm-lock.yaml - - - name: Install dependencies - if: ${{ inputs.install-dependencies == 'true' }} - shell: bash - run: pnpm --dir web install --frozen-lockfile + node-version-file: "./web/.nvmrc" + cache: true + run-install: | + - cwd: ./web + args: ['--frozen-lockfile'] diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 2af3b130ad..80f892589d 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -102,13 +102,11 @@ jobs: - name: Setup web environment if: steps.web-changes.outputs.any_changed == 'true' uses: ./.github/actions/setup-web - with: - node-version: "24" - name: ESLint autofix if: steps.web-changes.outputs.any_changed == 'true' run: | cd web - pnpm eslint --concurrency=2 --prune-suppressions --quiet || true + vp exec eslint --concurrency=2 --prune-suppressions --quiet || true - uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 # v1.3.3 diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index b284694530..868bacc6e5 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -88,7 +88,7 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web run: | - pnpm run lint:ci + vp run lint:ci # pnpm run lint:report # continue-on-error: true @@ -102,17 +102,17 @@ jobs: - name: Web tsslint if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run lint:tss + run: vp run lint:tss - name: Web type check if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run type-check + run: vp run type-check - name: Web dead code check if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run knip + run: vp run knip superlinter: name: SuperLinter diff --git a/.github/workflows/translate-i18n-claude.yml b/.github/workflows/translate-i18n-claude.yml index ff07313ebe..62724c84e5 100644 --- a/.github/workflows/translate-i18n-claude.yml +++ b/.github/workflows/translate-i18n-claude.yml @@ -50,8 +50,6 @@ jobs: - name: Setup web environment uses: ./.github/actions/setup-web - with: - install-dependencies: "false" - name: Detect changed files and generate diff id: detect_changes diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 47cced863a..fd2b941ce3 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -43,7 +43,7 @@ jobs: uses: ./.github/actions/setup-web - name: Run tests - run: pnpm vitest run --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --coverage + run: vp test run --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --coverage - name: Upload blob report if: ${{ !cancelled() }} @@ -84,7 +84,7 @@ jobs: merge-multiple: true - name: Merge reports - run: pnpm vitest --merge-reports --reporter=json --reporter=agent --coverage + run: vp test --merge-reports --reporter=json --reporter=agent --coverage - name: Check app/components diff coverage env: @@ -447,4 +447,4 @@ jobs: - name: Web build check if: steps.changed-files.outputs.any_changed == 'true' working-directory: ./web - run: pnpm run build + run: vp run build diff --git a/web/knip.config.ts b/web/knip.config.ts index fffb057b41..b7090ef8b1 100644 --- a/web/knip.config.ts +++ b/web/knip.config.ts @@ -20,6 +20,8 @@ const config: KnipConfig = { '@iconify-json/*', '@storybook/addon-onboarding', + + '@voidzero-dev/vite-plus-core', ], rules: { files: 'warn', diff --git a/web/package.json b/web/package.json index 40659c1085..e4115db287 100644 --- a/web/package.json +++ b/web/package.json @@ -50,9 +50,9 @@ "start:vinext": "vinext start", "storybook": "storybook dev -p 6006", "storybook:build": "storybook build", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "test:watch": "vitest --watch", + "test": "vp test", + "test:coverage": "vp test --coverage", + "test:watch": "vp test --watch", "type-check": "tsc --noEmit", "type-check:tsgo": "tsgo --noEmit", "uglify-embed": "node ./bin/uglify-embed" @@ -213,6 +213,7 @@ "@vitejs/plugin-react": "6.0.0", "@vitejs/plugin-rsc": "0.5.21", "@vitest/coverage-v8": "4.1.0", + "@voidzero-dev/vite-plus-core": "0.1.11", "agentation": "2.3.2", "autoprefixer": "10.4.27", "code-inspector-plugin": "1.4.4", @@ -241,9 +242,10 @@ "typescript": "5.9.3", "uglify-js": "3.19.3", "vinext": "https://pkg.pr.new/vinext@18fe3ea", - "vite": "8.0.0", + "vite": "npm:@voidzero-dev/vite-plus-core@0.1.11", "vite-plugin-inspect": "11.3.3", - "vitest": "4.1.0", + "vite-plus": "0.1.11", + "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.11", "vitest-canvas-mock": "1.1.3" }, "pnpm": { @@ -293,6 +295,8 @@ "svgo@>=3.0.0,<3.3.3": "3.3.3", "tar@<=7.5.10": "7.5.11", "typed-array-buffer": "npm:@nolyfill/typed-array-buffer@^1.0.44", + "vite": "npm:@voidzero-dev/vite-plus-core@0.1.11", + "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.11", "which-typed-array": "npm:@nolyfill/which-typed-array@^1.0.44" }, "ignoredBuiltDependencies": [ diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 2231d6c8ad..b6dbedb067 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -50,6 +50,8 @@ overrides: svgo@>=3.0.0,<3.3.3: 3.3.3 tar@<=7.5.10: 7.5.11 typed-array-buffer: npm:@nolyfill/typed-array-buffer@^1.0.44 + vite: npm:@voidzero-dev/vite-plus-core@0.1.11 + vitest: npm:@voidzero-dev/vite-plus-test@0.1.11 which-typed-array: npm:@nolyfill/which-typed-array@^1.0.44 importers: @@ -377,7 +379,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 7.7.2 - version: 7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + version: 7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(typescript@5.9.3) '@chromatic-com/storybook': specifier: 5.0.1 version: 5.0.1(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) @@ -413,7 +415,7 @@ importers: version: 4.2.0 '@storybook/addon-docs': specifier: 10.2.17 - version: 10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + version: 10.2.17(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/addon-links': specifier: 10.2.17 version: 10.2.17(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) @@ -425,7 +427,7 @@ importers: version: 10.2.17(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@storybook/nextjs-vite': specifier: 10.2.17 - version: 10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + version: 10.2.17(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': specifier: 10.2.17 version: 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) @@ -509,13 +511,16 @@ importers: version: 7.0.0-dev.20260312.1 '@vitejs/plugin-react': specifier: 6.0.0 - version: 6.0.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 6.0.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) '@vitejs/plugin-rsc': specifier: 0.5.21 - version: 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + version: 4.1.0(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + '@voidzero-dev/vite-plus-core': + specifier: 0.1.11 + version: 0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) agentation: specifier: 2.3.2 version: 2.3.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -530,7 +535,7 @@ importers: version: 10.0.3(jiti@1.21.7) eslint-plugin-better-tailwindcss: specifier: 4.3.2 - version: 4.3.2(eslint@10.0.3(jiti@1.21.7))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3) + version: 4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3) eslint-plugin-hyoban: specifier: 0.14.1 version: 0.14.1(eslint@10.0.3(jiti@1.21.7)) @@ -599,19 +604,22 @@ importers: version: 3.19.3 vinext: specifier: https://pkg.pr.new/vinext@18fe3ea - version: https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3) vite: - specifier: 8.0.0 - version: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: npm:@voidzero-dev/vite-plus-core@0.1.11 + version: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-inspect: specifier: 11.3.3 - version: 11.3.3(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 11.3.3(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + vite-plus: + specifier: 0.1.11 + version: 0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) vitest: - specifier: 4.1.0 - version: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: npm:@voidzero-dev/vite-plus-test@0.1.11 + version: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vitest-canvas-mock: specifier: 1.1.3 - version: 1.1.3(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + version: 1.1.3(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) packages: @@ -1977,6 +1985,280 @@ packages: cpu: [x64] os: [win32] + '@oxfmt/binding-android-arm-eabi@0.40.0': + resolution: {integrity: sha512-S6zd5r1w/HmqR8t0CTnGjFTBLDq2QKORPwriCHxo4xFNuhmOTABGjPaNvCJJVnrKBLsohOeiDX3YqQfJPF+FXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.40.0': + resolution: {integrity: sha512-/mbS9UUP/5Vbl2D6osIdcYiP0oie63LKMoTyGj5hyMCK/SFkl3EhtyRAfdjPvuvHC0SXdW6ePaTKkBSq1SNcIw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.40.0': + resolution: {integrity: sha512-wRt8fRdfLiEhnRMBonlIbKrJWixoEmn6KCjKE9PElnrSDSXETGZfPb8ee+nQNTobXkCVvVLytp2o0obAsxl78Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.40.0': + resolution: {integrity: sha512-fzowhqbOE/NRy+AE5ob0+Y4X243WbWzDb00W+pKwD7d9tOqsAFbtWUwIyqqCoCLxj791m2xXIEeLH/3uz7zCCg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.40.0': + resolution: {integrity: sha512-agZ9ITaqdBjcerRRFEHB8s0OyVcQW8F9ZxsszjxzeSthQ4fcN2MuOtQFWec1ed8/lDa50jSLHVE2/xPmTgtCfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': + resolution: {integrity: sha512-ZM2oQ47p28TP1DVIp7HL1QoMUgqlBFHey0ksHct7tMXoU5BqjNvPWw7888azzMt25lnyPODVuye1wvNbvVUFOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.40.0': + resolution: {integrity: sha512-RBFPAxRAIsMisKM47Oe6Lwdv6agZYLz02CUhVCD1sOv5ajAcRMrnwCFBPWwGXpazToW2mjnZxFos8TuFjTU15A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.40.0': + resolution: {integrity: sha512-Nb2XbQ+wV3W2jSIihXdPj7k83eOxeSgYP3N/SRXvQ6ZYPIk6Q86qEh5Gl/7OitX3bQoQrESqm1yMLvZV8/J7dA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.40.0': + resolution: {integrity: sha512-tGmWhLD/0YMotCdfezlT6tC/MJG/wKpo4vnQ3Cq+4eBk/BwNv7EmkD0VkD5F/dYkT3b8FNU01X2e8vvJuWoM1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.40.0': + resolution: {integrity: sha512-rVbFyM3e7YhkVnp0IVYjaSHfrBWcTRWb60LEcdNAJcE2mbhTpbqKufx0FrhWfoxOrW/+7UJonAOShoFFLigDqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.40.0': + resolution: {integrity: sha512-3ZqBw14JtWeEoLiioJcXSJz8RQyPE+3jLARnYM1HdPzZG4vk+Ua8CUupt2+d+vSAvMyaQBTN2dZK+kbBS/j5mA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.40.0': + resolution: {integrity: sha512-JJ4PPSdcbGBjPvb+O7xYm2FmAsKCyuEMYhqatBAHMp/6TA6rVlf9Z/sYPa4/3Bommb+8nndm15SPFRHEPU5qFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.40.0': + resolution: {integrity: sha512-Kp0zNJoX9Ik77wUya2tpBY3W9f40VUoMQLWVaob5SgCrblH/t2xr/9B2bWHfs0WCefuGmqXcB+t0Lq77sbBmZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.40.0': + resolution: {integrity: sha512-7YTCNzleWTaQTqNGUNQ66qVjpoV6DjbCOea+RnpMBly2bpzrI/uu7Rr+2zcgRfNxyjXaFTVQKaRKjqVdeUfeVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.40.0': + resolution: {integrity: sha512-hWnSzJ0oegeOwfOEeejYXfBqmnRGHusgtHfCPzmvJvHTwy1s3Neo59UKc1CmpE3zxvrCzJoVHos0rr97GHMNPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.40.0': + resolution: {integrity: sha512-28sJC1lR4qtBJGzSRRbPnSW3GxU2+4YyQFE6rCmsUYqZ5XYH8jg0/w+CvEzQ8TuAQz5zLkcA25nFQGwoU0PT3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.40.0': + resolution: {integrity: sha512-cDkRnyT0dqwF5oIX1Cv59HKCeZQFbWWdUpXa3uvnHFT2iwYSSZspkhgjXjU6iDp5pFPaAEAe9FIbMoTgkTmKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.40.0': + resolution: {integrity: sha512-7rPemBJjqm5Gkv6ZRCPvK8lE6AqQ/2z31DRdWazyx2ZvaSgL7QGofHXHNouRpPvNsT9yxRNQJgigsWkc+0qg4w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.40.0': + resolution: {integrity: sha512-/Zmj0yTYSvmha6TG1QnoLqVT7ZMRDqXvFXXBQpIjteEwx9qvUYMBH2xbiOFhDeMUJkGwC3D6fdKsFtaqUvkwNA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint-tsgolint/darwin-arm64@0.16.0': + resolution: {integrity: sha512-WQt5lGwRPJBw7q2KNR0mSPDAaMmZmVvDlEEti96xLO7ONhyomQc6fBZxxwZ4qTFedjJnrHX94sFelZ4OKzS7UQ==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.16.0': + resolution: {integrity: sha512-VJo29XOzdkalvCTiE2v6FU3qZlgHaM8x8hUEVJGPU2i5W+FlocPpmn00+Ld2n7Q0pqIjyD5EyvZ5UmoIEJMfqg==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.16.0': + resolution: {integrity: sha512-MPfqRt1+XRHv9oHomcBMQ3KpTE+CSkZz14wUxDQoqTNdUlV0HWdzwIE9q65I3D9YyxEnqpM7j4qtDQ3apqVvbQ==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.16.0': + resolution: {integrity: sha512-XQSwVUsnwLokMhe1TD6IjgvW5WMTPzOGGkdFDtXWQmlN2YeTw94s/NN0KgDrn2agM1WIgAenEkvnm0u7NgwEyw==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.16.0': + resolution: {integrity: sha512-EWdlspQiiFGsP2AiCYdhg5dTYyAlj6y1nRyNI2dQWq4Q/LITFHiSRVPe+7m7K7lcsZCEz2icN/bCeSkZaORqIg==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.16.0': + resolution: {integrity: sha512-1ufk8cgktXJuJZHKF63zCHAkaLMwZrEXnZ89H2y6NO85PtOXqu4zbdNl0VBpPP3fCUuUBu9RvNqMFiv0VsbXWA==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.55.0': + resolution: {integrity: sha512-NhvgAhncTSOhRahQSCnkK/4YIGPjTmhPurQQ2dwt2IvwCMTvZRW5vF2K10UBOxFve4GZDMw6LtXZdC2qeuYIVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.55.0': + resolution: {integrity: sha512-P9iWRh+Ugqhg+D7rkc7boHX8o3H2h7YPcZHQIgvVBgnua5tk4LR2L+IBlreZs58/95cd2x3/004p5VsQM9z4SA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.55.0': + resolution: {integrity: sha512-esakkJIt7WFAhT30P/Qzn96ehFpzdZ1mNuzpOb8SCW7lI4oB8VsyQnkSHREM671jfpuBb/o2ppzBCx5l0jpgMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.55.0': + resolution: {integrity: sha512-xDMFRCCAEK9fOH6As2z8ELsC+VDGSFRHwIKVSilw+xhgLwTDFu37rtmRbmUlx8rRGS6cWKQPTc47AVxAZEVVPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.55.0': + resolution: {integrity: sha512-mYZqnwUD7ALCRxGenyLd1uuG+rHCL+OTT6S8FcAbVm/ZT2AZMGjvibp3F6k1SKOb2aeqFATmwRykrE41Q0GWVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.55.0': + resolution: {integrity: sha512-LcX6RYcF9vL9ESGwJW3yyIZ/d/ouzdOKXxCdey1q0XJOW1asrHsIg5MmyKdEBR4plQx+shvYeQne7AzW5f3T1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.55.0': + resolution: {integrity: sha512-C+8GS1rPtK+dI7mJFkqoRBkDuqbrNihnyYQsJPS9ez+8zF9JzfvU19lawqt4l/Y23o5uQswE/DORa8aiXUih3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.55.0': + resolution: {integrity: sha512-ErLE4XbmcCopA4/CIDiH6J1IAaDOMnf/KSx/aFObs4/OjAAM3sFKWGZ57pNOMxhhyBdcmcXwYymph9GwcpcqgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.55.0': + resolution: {integrity: sha512-/kp65avi6zZfqEng56TTuhiy3P/3pgklKIdf38yvYeJ9/PgEeRA2A2AqKAKbZBNAqUzrzHhz9jF6j/PZvhJzTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.55.0': + resolution: {integrity: sha512-A6pTdXwcEEwL/nmz0eUJ6WxmxcoIS+97GbH96gikAyre3s5deC7sts38ZVVowjS2QQFuSWkpA4ZmQC0jZSNvJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.55.0': + resolution: {integrity: sha512-clj0lnIN+V52G9tdtZl0LbdTSurnZ1NZj92Je5X4lC7gP5jiCSW+Y/oiDiSauBAD4wrHt2S7nN3pA0zfKYK/6Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.55.0': + resolution: {integrity: sha512-NNu08pllN5x/O94/sgR3DA8lbrGBnTHsINZZR0hcav1sj79ksTiKKm1mRzvZvacwQ0hUnGinFo+JO75ok2PxYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.55.0': + resolution: {integrity: sha512-BvfQz3PRlWZRoEZ17dZCqgQsMRdpzGZomJkVATwCIGhHVVeHJMQdmdXPSjcT1DCNUrOjXnVyj1RGDj5+/Je2+Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.55.0': + resolution: {integrity: sha512-ngSOoFCSBMKVQd24H8zkbcBNc7EHhjnF1sv3mC9NNXQ/4rRjI/4Dj9+9XoDZeFEkF1SX1COSBXF1b2Pr9rqdEw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.55.0': + resolution: {integrity: sha512-BDpP7W8GlaG7BR6QjGZAleYzxoyKc/D24spZIF2mB3XsfALQJJT/OBmP8YpeTb1rveFSBHzl8T7l0aqwkWNdGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.55.0': + resolution: {integrity: sha512-PS6GFvmde/pc3fCA2Srt51glr8Lcxhpf6WIBFfLphndjRrD34NEcses4TSxQrEcxYo6qVywGfylM0ZhSCF2gGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.55.0': + resolution: {integrity: sha512-P6JcLJGs/q1UOvDLzN8otd9JsH4tsuuPDv+p7aHqHM3PrKmYdmUvkNj4K327PTd35AYcznOCN+l4ZOaq76QzSw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.55.0': + resolution: {integrity: sha512-gzkk4zE2zsE+WmRxFOiAZHpCpUNDFytEakqNXoNHW+PnYEOTPKDdW6nrzgSeTbGKVPXNAKQnRnMgrh7+n3Xueg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.55.0': + resolution: {integrity: sha512-ZFALNow2/og75gvYzNP7qe+rREQ5xunktwA+lgykoozHZ6hw9bqg4fn5j2UvG4gIn1FXqrZHkOAXuPf5+GOYTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} engines: {node: '>= 10.0.0'} @@ -2357,101 +2639,6 @@ packages: resolution: {integrity: sha512-UuBOt7BOsKVOkFXRe4Ypd/lADuNIfqJXv8GvHqtXaTYXPPKkj2nS2zPllVsrtRjcomDhIJVBnZwfmlI222WH8g==} engines: {node: '>=14.0.0'} - '@rolldown/binding-android-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} @@ -2461,9 +2648,6 @@ packages: '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - '@rolldown/pluginutils@1.0.0-rc.9': - resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} - '@rollup/plugin-replace@6.0.3': resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} engines: {node: '>=14.0.0'} @@ -3448,44 +3632,144 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.1.0': - resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} - - '@vitest/mocker@4.1.0': - resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} '@vitest/pretty-format@4.1.0': resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} - '@vitest/runner@4.1.0': - resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} - - '@vitest/snapshot@4.1.0': - resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} - '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.1.0': - resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} - '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@vitest/utils@4.1.0': resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + '@voidzero-dev/vite-plus-core@0.1.11': + resolution: {integrity: sha512-feyYRSg3u8acYNC1fF4EGfgYZm2efZB8YWTjz4NrU0Ulhlni1C6COMwHSDVpu9F4Jh+WcSsBWL3ZC1WvLa7jCw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.21.2 + '@tsdown/exe': 0.21.2 + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.0.0-alpha.31 + esbuild: 0.27.2 + jiti: '>=1.21.0' + less: ^4.0.0 + publint: ^0.3.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + typescript: ^5.0.0 + unplugin-unused: ^0.5.0 + yaml: ^2.4.2 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + publint: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + yaml: + optional: true + + '@voidzero-dev/vite-plus-darwin-arm64@0.1.11': + resolution: {integrity: sha512-ENokEkMhDMJ9nM/tUDAXvtah/P3cAnEbkeKCCxJgFvTTGnGM8eBvP2qpJeTrfhy9ndIWihcsfMufszinLsfhUg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@voidzero-dev/vite-plus-darwin-x64@0.1.11': + resolution: {integrity: sha512-gOSGYtXq5qigDsiW+oCrefv4K8WUSnZ5vH+kPHDvpsMXlqxR0rY6xrJgkJ2tCkWdCig8YHVDascSV/cj4nGwsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.11': + resolution: {integrity: sha512-aDVe1vvhtXBqZdmCiCSm3DUl5/O+x5CeAcjPPTLSsEX79cSfvkD0UU26lQ8eX+pr3xVDEocJTtTLmOMVImGlyA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.11': + resolution: {integrity: sha512-rkaKCGq/CFML2M7c0ixUOuhE6qi961x84/ZFQhkUy2MJw3RP7R/M1BDyWr2qEq20SgRWLkffcWMni3P2JnmrBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@voidzero-dev/vite-plus-test@0.1.11': + resolution: {integrity: sha512-3kBfi/LyPOGnLCmvYtgM5GZVAyiJiYjgdm9Fu9WLLl56zcSljj0TBG19eaKY6v/j2VJ+7o80n/A/MPz46lzMFA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/ui': 4.1.0 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.11': + resolution: {integrity: sha512-MerozzH8QYY+V5l6ZQq+vrtx75rnPlmc+TauH5hL08oEWx7ScwfrNKyamnv5rg7HWBx/ryuaYaJCjODOu7MjSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.11': + resolution: {integrity: sha512-ubGlfvkfWT4Eivg3O2lxMyA6h7u1XZm4XdW3MUZIXXd9Q/iIRVJdSsEg78C/OZ3e8Qofszsro6P8ZrQo8ROQxg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -3803,6 +4087,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} @@ -3832,10 +4120,6 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - chalk@4.1.1: resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==} engines: {node: '>=10'} @@ -4817,10 +5101,6 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -6077,6 +6357,25 @@ packages: oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} + oxfmt@0.40.0: + resolution: {integrity: sha512-g0C3I7xUj4b4DcagevM9kgH6+pUHytikxUcn3/VUkvzTNaaXBeyZqb7IBsHwojeXm4mTBEC/aBjBTMVUkZwWUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + oxlint-tsgolint@0.16.0: + resolution: {integrity: sha512-4RuJK2jP08XwqtUu+5yhCbxEauCm6tv2MFHKEMsjbosK2+vy5us82oI3VLuHwbNyZG7ekZA26U2LLHnGR4frIA==} + hasBin: true + + oxlint@1.55.0: + resolution: {integrity: sha512-T+FjepiyWpaZMhekqRpH8Z3I4vNM610p6w+Vjfqgj5TZUxHXl7N8N5IPvmOU8U4XdTRxqtNNTh9Y4hLtr7yvFg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.15.0' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -6196,6 +6495,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@7.1.0: + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} + hasBin: true + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -6206,6 +6509,10 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + pnpm-workspace-yaml@1.6.0: resolution: {integrity: sha512-uUy4dK3E11sp7nK+hnT7uAWfkBMe00KaUw8OG3NuNlYQoTk4sc9pcdIy1+XIP85v9Tvr02mK3JPaNNrP0QyRaw==} @@ -6664,11 +6971,6 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rolldown@1.0.0-rc.9: - resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -6762,9 +7064,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -6838,9 +7137,6 @@ packages: engines: {node: '>=20.16.0'} hasBin: true - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.0.0: resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} @@ -7053,6 +7349,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} @@ -7404,6 +7704,11 @@ packages: storybook: ^0.0.0-0 || ^9.0.0 || ^10.0.0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vite-plus@0.1.11: + resolution: {integrity: sha512-mDUbWirSUWtS/diQiq1QkHGsMNQWu90kSH5s7RWqVnV9s1PRxQ1IcH6mIeOG7YzPJlfO1vQbONZRsOfdyj9IKw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -7417,49 +7722,6 @@ packages: peerDependencies: vite: '*' - vite@8.0.0: - resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.0.0-alpha.31 - esbuild: 0.27.2 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vitefu@1.1.2: resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} peerDependencies: @@ -7473,41 +7735,6 @@ packages: peerDependencies: vitest: ^3.0.0 || ^4.0.0 - vitest@4.1.0: - resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.0 - '@vitest/browser-preview': 4.1.0 - '@vitest/browser-webdriverio': 4.1.0 - '@vitest/ui': 4.1.0 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -7599,11 +7826,6 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -7881,17 +8103,17 @@ snapshots: idb: 8.0.0 tslib: 2.8.1 - '@antfu/eslint-config@7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': + '@antfu/eslint-config@7.7.2(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.1.6)(@typescript-eslint/rule-tester@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3))(@typescript-eslint/utils@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(typescript@5.9.3)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 1.1.0 - '@e18e/eslint-plugin': 0.2.0(eslint@10.0.3(jiti@1.21.7)) + '@e18e/eslint-plugin': 0.2.0(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0)) '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.0.3(jiti@1.21.7)) '@eslint/markdown': 7.5.1 '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.3(jiti@1.21.7)) '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/parser': 8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) + '@vitest/eslint-plugin': 1.6.10(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) ansis: 4.2.0 cac: 7.0.0 eslint: 10.0.3(jiti@1.21.7) @@ -8228,11 +8450,12 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@e18e/eslint-plugin@0.2.0(eslint@10.0.3(jiti@1.21.7))': + '@e18e/eslint-plugin@0.2.0(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))': dependencies: eslint-plugin-depend: 1.5.0(eslint@10.0.3(jiti@1.21.7)) optionalDependencies: eslint: 10.0.3(jiti@1.21.7) + oxlint: 1.55.0(oxlint-tsgolint@0.16.0) '@egoist/tailwindcss-icons@1.9.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))': dependencies: @@ -8753,11 +8976,11 @@ snapshots: dependencies: minipass: 7.1.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3)': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' optionalDependencies: typescript: 5.9.3 @@ -9251,6 +9474,138 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.19.1': optional: true + '@oxfmt/binding-android-arm-eabi@0.40.0': + optional: true + + '@oxfmt/binding-android-arm64@0.40.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.40.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.40.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.40.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.40.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.40.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.40.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.40.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.40.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.40.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.40.0': + optional: true + + '@oxlint-tsgolint/darwin-arm64@0.16.0': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.16.0': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.16.0': + optional: true + + '@oxlint-tsgolint/linux-x64@0.16.0': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.16.0': + optional: true + + '@oxlint-tsgolint/win32-x64@0.16.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.55.0': + optional: true + + '@oxlint/binding-android-arm64@1.55.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.55.0': + optional: true + + '@oxlint/binding-darwin-x64@1.55.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.55.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.55.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.55.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.55.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.55.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.55.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.55.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.55.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.55.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.55.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.55.0': + optional: true + '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -9616,61 +9971,12 @@ snapshots: '@rgrove/parse-xml@4.2.0': {} - '@rolldown/binding-android-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - optional: true - '@rolldown/pluginutils@1.0.0-rc.3': {} '@rolldown/pluginutils@1.0.0-rc.5': {} '@rolldown/pluginutils@1.0.0-rc.7': {} - '@rolldown/pluginutils@1.0.0-rc.9': {} - '@rollup/plugin-replace@6.0.3(rollup@4.59.0)': dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -9840,10 +10146,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.2.17(@types/react@19.2.14)(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/addon-docs@10.2.17(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) - '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/csf-plugin': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@storybook/react-dom-shim': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) react: 19.2.4 @@ -9873,25 +10179,25 @@ snapshots: storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/builder-vite@10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/csf-plugin': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/csf-plugin': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/csf-plugin@10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 rollup: 4.59.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' webpack: 5.105.4(esbuild@0.27.2)(uglify-js@3.19.3) '@storybook/global@5.0.0': {} @@ -9901,18 +10207,18 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@storybook/nextjs-vite@10.2.17(@babel/core@7.29.0)(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/nextjs-vite@10.2.17(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/builder-vite': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@storybook/react-vite': 10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/react-vite': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-plugin-storybook-nextjs: 3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-plugin-storybook-nextjs: 3.2.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -9929,11 +10235,11 @@ snapshots: react-dom: 19.2.4(react@19.2.4) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/react-vite@10.2.17(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/react-vite@10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) - '@storybook/builder-vite': 10.2.17(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/builder-vite': 10.2.17(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': 10.2.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -9943,7 +10249,7 @@ snapshots: resolve: 1.22.11 storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tsconfig-paths: 4.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - esbuild - rollup @@ -10669,7 +10975,7 @@ snapshots: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vitejs/plugin-react@5.1.4(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -10677,16 +10983,16 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@6.0.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -10698,12 +11004,12 @@ snapshots: srvx: 0.11.9 strip-literal: 3.1.0 turbo-stream: 3.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vitefu: 1.1.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vitefu: 1.1.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) optionalDependencies: react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': + '@vitest/coverage-v8@4.1.0(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.0 @@ -10715,16 +11021,16 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.0.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vitest: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - '@vitest/eslint-plugin@1.6.10(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': + '@vitest/eslint-plugin@1.6.10(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.57.0 '@typescript-eslint/utils': 8.57.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) eslint: 10.0.3(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vitest: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color @@ -10736,23 +11042,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/expect@4.1.0': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 - chai: 6.2.2 - tinyrainbow: 3.0.3 - - '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@vitest/spy': 4.1.0 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -10761,24 +11050,10 @@ snapshots: dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.1.0': - dependencies: - '@vitest/utils': 4.1.0 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.0': - dependencies: - '@vitest/pretty-format': 4.1.0 - '@vitest/utils': 4.1.0 - magic-string: 0.30.21 - pathe: 2.0.3 - '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 - '@vitest/spy@4.1.0': {} - '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -10791,6 +11066,81 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.0.3 + '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': + dependencies: + '@oxc-project/runtime': 0.115.0 + '@oxc-project/types': 0.115.0 + lightningcss: 1.32.0 + postcss: 8.5.8 + optionalDependencies: + '@types/node': 25.5.0 + esbuild: 0.27.2 + fsevents: 2.3.3 + jiti: 1.21.7 + sass: 1.98.0 + terser: 5.46.0 + tsx: 4.21.0 + typescript: 5.9.3 + yaml: 2.8.2 + + '@voidzero-dev/vite-plus-darwin-arm64@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-darwin-x64@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@voidzero-dev/vite-plus-core': 0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + es-module-lexer: 1.7.0 + obug: 2.1.1 + pixelmatch: 7.1.0 + pngjs: 7.0.0 + sirv: 3.0.2 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + ws: 8.19.0 + optionalDependencies: + '@types/node': 25.5.0 + jsdom: 28.1.0(canvas@3.2.1) + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@tsdown/css' + - '@tsdown/exe' + - '@vitejs/devtools' + - bufferutil + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - utf-8-validate + - yaml + + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.11': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.11': + optional: true + '@volar/language-core@2.4.28': dependencies: '@volar/source-map': 2.4.28 @@ -11124,6 +11474,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + cac@7.0.0: {} callsites@3.1.0: {} @@ -11150,8 +11502,6 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 - chai@6.2.2: {} - chalk@4.1.1: dependencies: ansi-styles: 4.3.0 @@ -11843,7 +12193,7 @@ snapshots: dependencies: eslint: 10.0.3(jiti@1.21.7) - eslint-plugin-better-tailwindcss@4.3.2(eslint@10.0.3(jiti@1.21.7))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3): + eslint-plugin-better-tailwindcss@4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.16.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3): dependencies: '@eslint/css-tree': 3.6.9 '@valibot/to-json-schema': 1.5.0(valibot@1.2.0(typescript@5.9.3)) @@ -11856,6 +12206,7 @@ snapshots: valibot: 1.2.0(typescript@5.9.3) optionalDependencies: eslint: 10.0.3(jiti@1.21.7) + oxlint: 1.55.0(oxlint-tsgolint@0.16.0) transitivePeerDependencies: - typescript @@ -12359,8 +12710,6 @@ snapshots: expand-template@2.0.3: optional: true - expect-type@1.3.0: {} - exsolve@1.0.8: {} extend@3.0.2: {} @@ -13954,6 +14303,62 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1 '@oxc-resolver/binding-win32-x64-msvc': 11.19.1 + oxfmt@0.40.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.40.0 + '@oxfmt/binding-android-arm64': 0.40.0 + '@oxfmt/binding-darwin-arm64': 0.40.0 + '@oxfmt/binding-darwin-x64': 0.40.0 + '@oxfmt/binding-freebsd-x64': 0.40.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.40.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.40.0 + '@oxfmt/binding-linux-arm64-gnu': 0.40.0 + '@oxfmt/binding-linux-arm64-musl': 0.40.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.40.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.40.0 + '@oxfmt/binding-linux-riscv64-musl': 0.40.0 + '@oxfmt/binding-linux-s390x-gnu': 0.40.0 + '@oxfmt/binding-linux-x64-gnu': 0.40.0 + '@oxfmt/binding-linux-x64-musl': 0.40.0 + '@oxfmt/binding-openharmony-arm64': 0.40.0 + '@oxfmt/binding-win32-arm64-msvc': 0.40.0 + '@oxfmt/binding-win32-ia32-msvc': 0.40.0 + '@oxfmt/binding-win32-x64-msvc': 0.40.0 + + oxlint-tsgolint@0.16.0: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.16.0 + '@oxlint-tsgolint/darwin-x64': 0.16.0 + '@oxlint-tsgolint/linux-arm64': 0.16.0 + '@oxlint-tsgolint/linux-x64': 0.16.0 + '@oxlint-tsgolint/win32-arm64': 0.16.0 + '@oxlint-tsgolint/win32-x64': 0.16.0 + + oxlint@1.55.0(oxlint-tsgolint@0.16.0): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.55.0 + '@oxlint/binding-android-arm64': 1.55.0 + '@oxlint/binding-darwin-arm64': 1.55.0 + '@oxlint/binding-darwin-x64': 1.55.0 + '@oxlint/binding-freebsd-x64': 1.55.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.55.0 + '@oxlint/binding-linux-arm-musleabihf': 1.55.0 + '@oxlint/binding-linux-arm64-gnu': 1.55.0 + '@oxlint/binding-linux-arm64-musl': 1.55.0 + '@oxlint/binding-linux-ppc64-gnu': 1.55.0 + '@oxlint/binding-linux-riscv64-gnu': 1.55.0 + '@oxlint/binding-linux-riscv64-musl': 1.55.0 + '@oxlint/binding-linux-s390x-gnu': 1.55.0 + '@oxlint/binding-linux-x64-gnu': 1.55.0 + '@oxlint/binding-linux-x64-musl': 1.55.0 + '@oxlint/binding-openharmony-arm64': 1.55.0 + '@oxlint/binding-win32-arm64-msvc': 1.55.0 + '@oxlint/binding-win32-ia32-msvc': 1.55.0 + '@oxlint/binding-win32-x64-msvc': 1.55.0 + oxlint-tsgolint: 0.16.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -14070,6 +14475,10 @@ snapshots: pirates@4.0.7: {} + pixelmatch@7.1.0: + dependencies: + pngjs: 7.0.0 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -14084,6 +14493,8 @@ snapshots: pluralize@8.0.0: {} + pngjs@7.0.0: {} + pnpm-workspace-yaml@1.6.0: dependencies: yaml: 2.8.2 @@ -14635,27 +15046,6 @@ snapshots: robust-predicates@3.0.2: {} - rolldown@1.0.0-rc.9: - dependencies: - '@oxc-project/types': 0.115.0 - '@rolldown/pluginutils': 1.0.0-rc.9 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-x64': 1.0.0-rc.9 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.9 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.9 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.9 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.9 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 - rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -14803,8 +15193,6 @@ snapshots: shebang-regex@3.0.0: {} - siginfo@2.0.0: {} - signal-exit@4.1.0: {} simple-concat@1.0.1: @@ -14873,8 +15261,6 @@ snapshots: srvx@0.11.9: {} - stackback@0.0.2: {} - std-env@4.0.0: {} storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -15141,6 +15527,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@2.1.0: {} + tinyrainbow@2.0.0: {} tinyrainbow@3.0.3: {} @@ -15419,36 +15807,36 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vinext@https://pkg.pr.new/vinext@18fe3ea(@mdx-js/rollup@3.1.1(rollup@4.59.0))(@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4))(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(typescript@5.9.3): dependencies: '@unpic/react': 1.0.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vercel/og': 0.8.6 - '@vitejs/plugin-react': 5.1.4(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-react': 5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) magic-string: 0.30.21 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rsc-html-stream: 0.0.7 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-commonjs: 0.10.4 - vite-tsconfig-paths: 6.1.1(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) optionalDependencies: '@mdx-js/rollup': 3.1.1(rollup@4.59.0) - '@vitejs/plugin-rsc': 0.5.21(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-rsc': 0.5.21(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) transitivePeerDependencies: - next - supports-color - typescript - vite-dev-rpc@1.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-dev-rpc@1.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: birpc: 2.9.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-hot-client: 2.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-hot-client: 2.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) - vite-hot-client@2.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-hot-client@2.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-commonjs@0.10.4: dependencies: @@ -15463,7 +15851,7 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 - vite-plugin-inspect@11.3.3(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-inspect@11.3.3(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: ansis: 4.2.0 debug: 4.4.3 @@ -15473,12 +15861,12 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-dev-rpc: 1.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-dev-rpc: 1.1.0(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - vite-plugin-storybook-nextjs@3.2.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-storybook-nextjs@3.2.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3): dependencies: '@next/env': 16.0.0 image-size: 2.0.2 @@ -15487,88 +15875,88 @@ snapshots: next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) storybook: 10.2.17(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plus@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + dependencies: + '@oxc-project/types': 0.115.0 + '@voidzero-dev/vite-plus-core': 0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + '@voidzero-dev/vite-plus-test': 0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + cac: 6.7.14 + cross-spawn: 7.0.6 + oxfmt: 0.40.0 + oxlint: 1.55.0(oxlint-tsgolint@0.16.0) + oxlint-tsgolint: 0.16.0 + picocolors: 1.1.1 + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 0.1.11 + '@voidzero-dev/vite-plus-darwin-x64': 0.1.11 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.11 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.11 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.11 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.11 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@tsdown/css' + - '@tsdown/exe' + - '@types/node' + - '@vitejs/devtools' + - '@vitest/ui' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - utf-8-validate + - vite + - yaml + + vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - typescript - vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@oxc-project/runtime': 0.115.0 - lightningcss: 1.32.0 - picomatch: 4.0.3 - postcss: 8.5.8 - rolldown: 1.0.0-rc.9 - tinyglobby: 0.2.15 + vitefu@1.1.2(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): optionalDependencies: - '@types/node': 25.5.0 - esbuild: 0.27.2 - fsevents: 2.3.3 - jiti: 1.21.7 - sass: 1.98.0 - terser: 5.46.0 - tsx: 4.21.0 - yaml: 2.8.2 + vite: '@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vitefu@1.1.2(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): - optionalDependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - - vitest-canvas-mock@1.1.3(vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))): + vitest-canvas-mock@1.1.3(@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: 4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - - vitest@4.1.0(@types/node@25.5.0)(jsdom@28.1.0(canvas@3.2.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): - dependencies: - '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.1.0 - '@vitest/runner': 4.1.0 - '@vitest/snapshot': 4.1.0 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.5.0 - jsdom: 28.1.0(canvas@3.2.1) - transitivePeerDependencies: - - msw + vitest: '@voidzero-dev/vite-plus-test@0.1.11(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.11(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@28.1.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' void-elements@3.1.0: {} @@ -15674,11 +16062,6 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - word-wrap@1.2.5: {} wrap-ansi@9.0.2: diff --git a/web/vite.config.ts b/web/vite.config.ts index 3a61264e3c..39dff85dae 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,11 +1,9 @@ -/// - import path from 'node:path' import { fileURLToPath } from 'node:url' import react from '@vitejs/plugin-react' import vinext from 'vinext' -import { defineConfig } from 'vite' import Inspect from 'vite-plugin-inspect' +import { defineConfig } from 'vite-plus' import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr' import { collectComponentCoverageExcludedFiles } from './scripts/component-coverage-filters.mjs' From 7e1dc3c122e0ac2f767678a7cd1b571d1664d5e9 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Fri, 13 Mar 2026 19:10:24 +0800 Subject: [PATCH 10/37] refactor(web): split share text-generation and add high-coverage tests (#33408) Co-authored-by: CodingOnStar Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../share/text-generation-index-flow.test.tsx | 235 ++++++ .../app/overview/settings/index.spec.tsx | 56 +- .../app/overview/settings/index.tsx | 59 +- .../text-generation-result-panel.spec.tsx | 190 +++++ .../text-generation-sidebar.spec.tsx | 261 +++++++ .../use-text-generation-app-state.spec.ts | 298 +++++++ .../use-text-generation-batch.spec.ts | 314 ++++++++ .../hooks/use-text-generation-app-state.ts | 158 ++++ .../hooks/use-text-generation-batch.ts | 270 +++++++ .../share/text-generation/index.tsx | 738 ++++-------------- .../share/text-generation/run-once/index.tsx | 7 +- .../text-generation-result-panel.tsx | 195 +++++ .../text-generation-sidebar.tsx | 177 +++++ .../components/share/text-generation/types.ts | 23 +- web/eslint-suppressions.json | 9 - 15 files changed, 2354 insertions(+), 636 deletions(-) create mode 100644 web/__tests__/share/text-generation-index-flow.test.tsx create mode 100644 web/app/components/share/text-generation/__tests__/text-generation-result-panel.spec.tsx create mode 100644 web/app/components/share/text-generation/__tests__/text-generation-sidebar.spec.tsx create mode 100644 web/app/components/share/text-generation/hooks/__tests__/use-text-generation-app-state.spec.ts create mode 100644 web/app/components/share/text-generation/hooks/__tests__/use-text-generation-batch.spec.ts create mode 100644 web/app/components/share/text-generation/hooks/use-text-generation-app-state.ts create mode 100644 web/app/components/share/text-generation/hooks/use-text-generation-batch.ts create mode 100644 web/app/components/share/text-generation/text-generation-result-panel.tsx create mode 100644 web/app/components/share/text-generation/text-generation-sidebar.tsx diff --git a/web/__tests__/share/text-generation-index-flow.test.tsx b/web/__tests__/share/text-generation-index-flow.test.tsx new file mode 100644 index 0000000000..3292474bec --- /dev/null +++ b/web/__tests__/share/text-generation-index-flow.test.tsx @@ -0,0 +1,235 @@ +import type { AccessMode } from '@/models/access-control' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import * as React from 'react' +import TextGeneration from '@/app/components/share/text-generation' + +const useSearchParamsMock = vi.fn(() => new URLSearchParams()) + +vi.mock('next/navigation', () => ({ + useSearchParams: () => useSearchParamsMock(), +})) + +vi.mock('@/hooks/use-breakpoints', () => ({ + default: vi.fn(() => 'pc'), + MediaType: { pc: 'pc', pad: 'pad', mobile: 'mobile' }, +})) + +vi.mock('@/hooks/use-app-favicon', () => ({ + useAppFavicon: vi.fn(), +})) + +vi.mock('@/hooks/use-document-title', () => ({ + default: vi.fn(), +})) + +vi.mock('@/i18n-config/client', () => ({ + changeLanguage: vi.fn(() => Promise.resolve()), +})) + +vi.mock('@/app/components/share/text-generation/run-once', () => ({ + default: ({ + inputs, + onInputsChange, + onSend, + runControl, + }: { + inputs: Record + onInputsChange: (inputs: Record) => void + onSend: () => void + runControl?: { isStopping: boolean } | null + }) => ( +
+ {String(inputs.name ?? '')} + + + {runControl ? 'stop-ready' : 'idle'} +
+ ), +})) + +vi.mock('@/app/components/share/text-generation/run-batch', () => ({ + default: ({ onSend }: { onSend: (data: string[][]) => void }) => ( + + ), +})) + +vi.mock('@/app/components/app/text-generate/saved-items', () => ({ + default: ({ list }: { list: { id: string }[] }) =>
{list.length}
, +})) + +vi.mock('@/app/components/share/text-generation/menu-dropdown', () => ({ + default: () =>
, +})) + +vi.mock('@/app/components/share/text-generation/result', () => { + const MockResult = ({ + isCallBatchAPI, + onRunControlChange, + onRunStart, + taskId, + }: { + isCallBatchAPI: boolean + onRunControlChange?: (control: { onStop: () => void, isStopping: boolean } | null) => void + onRunStart: () => void + taskId?: number + }) => { + const runControlRef = React.useRef(false) + + React.useEffect(() => { + onRunStart() + }, [onRunStart]) + + React.useEffect(() => { + if (!isCallBatchAPI && !runControlRef.current) { + runControlRef.current = true + onRunControlChange?.({ onStop: vi.fn(), isStopping: false }) + } + }, [isCallBatchAPI, onRunControlChange]) + + return
+ } + + return { + default: MockResult, + } +}) + +const fetchSavedMessageMock = vi.fn() + +vi.mock('@/service/share', async () => { + const actual = await vi.importActual('@/service/share') + return { + ...actual, + fetchSavedMessage: (...args: Parameters) => fetchSavedMessageMock(...args), + removeMessage: vi.fn(), + saveMessage: vi.fn(), + } +}) + +const mockSystemFeatures = { + branding: { + enabled: false, + workspace_logo: null, + }, +} + +const mockWebAppState = { + appInfo: { + app_id: 'app-123', + site: { + title: 'Text Generation', + description: 'Share description', + default_language: 'en-US', + icon_type: 'emoji', + icon: 'robot', + icon_background: '#fff', + icon_url: '', + }, + custom_config: { + remove_webapp_brand: false, + replace_webapp_logo: '', + }, + }, + appParams: { + user_input_form: [ + { + 'text-input': { + label: 'Name', + variable: 'name', + required: true, + max_length: 48, + default: '', + hide: false, + }, + }, + ], + more_like_this: { + enabled: true, + }, + file_upload: { + enabled: false, + number_limits: 2, + detail: 'low', + allowed_upload_methods: ['local_file'], + }, + text_to_speech: { + enabled: true, + }, + system_parameters: { + image_file_size_limit: 10, + }, + }, + webAppAccessMode: 'public' as AccessMode, +} + +vi.mock('@/context/global-public-context', () => ({ + useGlobalPublicStore: (selector: (state: { systemFeatures: typeof mockSystemFeatures }) => unknown) => + selector({ systemFeatures: mockSystemFeatures }), +})) + +vi.mock('@/context/web-app-context', () => ({ + useWebAppStore: (selector: (state: typeof mockWebAppState) => unknown) => selector(mockWebAppState), +})) + +describe('TextGeneration', () => { + beforeEach(() => { + vi.clearAllMocks() + useSearchParamsMock.mockReturnValue(new URLSearchParams()) + fetchSavedMessageMock.mockResolvedValue({ + data: [{ id: 'saved-1' }, { id: 'saved-2' }], + }) + }) + + it('should switch between create, batch, and saved tabs after app state loads', async () => { + render() + + await waitFor(() => { + expect(screen.getByTestId('run-once-mock')).toBeInTheDocument() + }) + expect(screen.getByTestId('run-once-input-name')).toHaveTextContent('') + + fireEvent.click(screen.getByRole('button', { name: 'change-inputs' })) + await waitFor(() => { + expect(screen.getByTestId('run-once-input-name')).toHaveTextContent('Gamma') + }) + + fireEvent.click(screen.getByTestId('tab-header-item-batch')) + expect(screen.getByRole('button', { name: 'run-batch' })).toBeInTheDocument() + + fireEvent.click(screen.getByTestId('tab-header-item-saved')) + expect(screen.getByTestId('saved-items-mock')).toHaveTextContent('2') + + fireEvent.click(screen.getByTestId('tab-header-item-create')) + expect(screen.getByTestId('run-once-mock')).toBeInTheDocument() + }) + + it('should wire single-run stop control and clear it when batch execution starts', async () => { + render() + + await waitFor(() => { + expect(screen.getByTestId('run-once-mock')).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole('button', { name: 'run-once' })) + await waitFor(() => { + expect(screen.getByText('stop-ready')).toBeInTheDocument() + }) + expect(screen.getByTestId('result-single')).toBeInTheDocument() + + fireEvent.click(screen.getByTestId('tab-header-item-batch')) + fireEvent.click(screen.getByRole('button', { name: 'run-batch' })) + await waitFor(() => { + expect(screen.getByText('idle')).toBeInTheDocument() + }) + expect(screen.getByTestId('result-task-1')).toBeInTheDocument() + expect(screen.getByTestId('result-task-2')).toBeInTheDocument() + }) +}) diff --git a/web/app/components/app/overview/settings/index.spec.tsx b/web/app/components/app/overview/settings/index.spec.tsx index d98e02ad57..b849b4f015 100644 --- a/web/app/components/app/overview/settings/index.spec.tsx +++ b/web/app/components/app/overview/settings/index.spec.tsx @@ -6,7 +6,7 @@ import type { ModalContextState } from '@/context/modal-context' import type { ProviderContextState } from '@/context/provider-context' import type { AppDetailResponse } from '@/models/app' import type { AppSSO } from '@/types/app' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import { Plan } from '@/app/components/billing/type' import { baseProviderContextValue } from '@/context/provider-context' import { AppModeEnum } from '@/types/app' @@ -131,6 +131,10 @@ describe('SettingsModal', () => { }) }) + afterEach(() => { + vi.useRealTimers() + }) + it('should render the modal and expose the expanded settings section', async () => { renderSettingsModal() expect(screen.getByText('appOverview.overview.appInfo.settings.title')).toBeInTheDocument() @@ -212,4 +216,54 @@ describe('SettingsModal', () => { })) expect(mockOnClose).toHaveBeenCalled() }) + + it('should clear the delayed hide-more timer when the modal unmounts after closing', () => { + vi.useFakeTimers() + const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout') + const { unmount } = renderSettingsModal() + + fireEvent.click(screen.getByText('appOverview.overview.appInfo.settings.more.entry')) + fireEvent.click(screen.getByText('common.operation.cancel')) + unmount() + + expect(clearTimeoutSpy).toHaveBeenCalled() + vi.runAllTimers() + }) + + it('should replace the pending hide-more timer and clear the ref after the timeout completes', async () => { + const hideCallbacks: Array<() => void> = [] + const originalSetTimeout = globalThis.setTimeout + const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout').mockImplementation((( + callback: TimerHandler, + delay?: number, + ...args: unknown[] + ) => { + if (delay === 200) { + hideCallbacks.push(() => { + if (typeof callback === 'function') + callback(...args) + }) + return hideCallbacks.length as unknown as ReturnType + } + + return originalSetTimeout(callback, delay, ...args) + }) as unknown as typeof setTimeout) + const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout') + renderSettingsModal() + + act(() => { + fireEvent.click(screen.getByText('common.operation.cancel')) + fireEvent.click(screen.getByText('common.operation.cancel')) + }) + + expect(clearTimeoutSpy).toHaveBeenCalled() + expect(hideCallbacks.length).toBeGreaterThanOrEqual(2) + + act(() => { + hideCallbacks.at(-1)?.() + }) + + setTimeoutSpy.mockRestore() + clearTimeoutSpy.mockRestore() + }) }) diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index 92bfdc5d31..f7c9e309ab 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -6,7 +6,7 @@ import type { AppIconType, AppSSO, Language } from '@/types/app' import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react' import Link from 'next/link' import * as React from 'react' -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import AppIcon from '@/app/components/base/app-icon' @@ -99,6 +99,7 @@ const SettingsModal: FC = ({ const [language, setLanguage] = useState(default_language) const [saveLoading, setSaveLoading] = useState(false) const { t } = useTranslation() + const hideMoreTimerRef = useRef | null>(null) const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [appIcon, setAppIcon] = useState( @@ -137,10 +138,22 @@ const SettingsModal: FC = ({ : { type: 'emoji', icon, background: icon_background! }) }, [appInfo, chat_color_theme, chat_color_theme_inverted, copyright, custom_disclaimer, default_language, description, icon, icon_background, icon_type, icon_url, privacy_policy, show_workflow_steps, title, use_icon_as_answer_icon]) + useEffect(() => { + return () => { + if (hideMoreTimerRef.current) { + clearTimeout(hideMoreTimerRef.current) + hideMoreTimerRef.current = null + } + } + }, []) + const onHide = () => { onClose() - setTimeout(() => { + if (hideMoreTimerRef.current) + clearTimeout(hideMoreTimerRef.current) + hideMoreTimerRef.current = setTimeout(() => { setIsShowMore(false) + hideMoreTimerRef.current = null }, 200) } @@ -231,12 +244,12 @@ const SettingsModal: FC = ({ {/* header */}
-
{t(`${prefixSettings}.title`, { ns: 'appOverview' })}
+
{t(`${prefixSettings}.title`, { ns: 'appOverview' })}
-
+
{t(`${prefixSettings}.modalTip`, { ns: 'appOverview' })}
@@ -245,7 +258,7 @@ const SettingsModal: FC = ({ {/* name & icon */}
-
{t(`${prefixSettings}.webName`, { ns: 'appOverview' })}
+
{t(`${prefixSettings}.webName`, { ns: 'appOverview' })}
= ({
{/* description */}
-
{t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}
+
{t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}