From 8c8ad02a6f88721da712beff58e5af58915e6844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Mon, 20 Apr 2026 15:12:51 +0800 Subject: [PATCH 001/253] chore: migrate workflow node title tooltip (#35418) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 3 -- web/app/components/workflow/run/node.tsx | 55 ++++++++++++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index e16a1be0f7..798ae1ec28 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -6068,9 +6068,6 @@ } }, "web/app/components/workflow/run/node.tsx": { - "no-restricted-imports": { - "count": 1 - }, "react/set-state-in-effect": { "count": 1 } diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index 6f8897e788..b683467d5f 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -8,6 +8,7 @@ import type { NodeTracing, } from '@/types/workflow' import { cn } from '@langgenius/dify-ui/cn' +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiAlertFill, RiArrowRightSLine, @@ -16,9 +17,8 @@ import { RiLoader2Line, RiPauseCircleFill, } from '@remixicon/react' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' @@ -68,6 +68,8 @@ const NodePanel: FC = ({ return doSetCollapseState(state) }, [hideProcessDetail]) + const titleRef = useRef(null) + const [isTitleTruncated, setIsTitleTruncated] = useState(false) const { t } = useTranslation() const docLink = useDocLink() @@ -92,6 +94,27 @@ const NodePanel: FC = ({ setCollapseState(!nodeInfo.expand) }, [nodeInfo.expand, setCollapseState]) + useEffect(() => { + const titleElement = titleRef.current + if (!titleElement) + return + + let frameId = 0 + const updateIsTitleTruncated = () => { + setIsTitleTruncated(titleElement.scrollWidth > titleElement.clientWidth) + } + + frameId = requestAnimationFrame(updateIsTitleTruncated) + + const resizeObserver = new ResizeObserver(updateIsTitleTruncated) + resizeObserver.observe(titleElement) + + return () => { + cancelAnimationFrame(frameId) + resizeObserver.disconnect() + } + }, [nodeInfo.title]) + const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration && !!nodeInfo.details?.length const isLoopNode = nodeInfo.node_type === BlockEnum.Loop && !!nodeInfo.details?.length const isRetryNode = hasRetryNode(nodeInfo.node_type) && !!nodeInfo.retryDetail?.length @@ -132,18 +155,24 @@ const NodePanel: FC = ({ /> )} - + + {nodeInfo.title} + + )} + /> +
{nodeInfo.title}
- } - > -
- {nodeInfo.title} -
+
{!['running', 'paused'].includes(nodeInfo.status) && !hideInfo && (
From 1a7e46368e3af5e7d6360cde574fe5323b1fc539 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Mon, 20 Apr 2026 15:25:49 +0800 Subject: [PATCH 002/253] feat(amplitude): integrate AmplitudeProvider and refactor initialization logic (#35415) Co-authored-by: CodingOnStar --- web/app/(commonLayout)/layout.tsx | 2 - web/app/account/(commonLayout)/layout.tsx | 2 - .../base/amplitude/AmplitudeProvider.tsx | 73 ++--------------- .../__tests__/AmplitudeProvider.spec.tsx | 13 +++ .../base/amplitude/__tests__/init.spec.ts | 61 ++++++++++++++ web/app/components/base/amplitude/init.ts | 82 +++++++++++++++++++ web/app/layout.tsx | 2 + 7 files changed, 164 insertions(+), 71 deletions(-) create mode 100644 web/app/components/base/amplitude/__tests__/init.spec.ts create mode 100644 web/app/components/base/amplitude/init.ts diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index 2467f35b7b..699d2a4348 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -2,7 +2,6 @@ import type { ReactNode } from 'react' import * as React from 'react' import { AppInitializer } from '@/app/components/app-initializer' import InSiteMessageNotification from '@/app/components/app/in-site-message/notification' -import AmplitudeProvider from '@/app/components/base/amplitude' import GA, { GaType } from '@/app/components/base/ga' import Zendesk from '@/app/components/base/zendesk' import { GotoAnything } from '@/app/components/goto-anything' @@ -20,7 +19,6 @@ const Layout = ({ children }: { children: ReactNode }) => { return ( <> - diff --git a/web/app/account/(commonLayout)/layout.tsx b/web/app/account/(commonLayout)/layout.tsx index 8fdbd8a238..f116cd00f9 100644 --- a/web/app/account/(commonLayout)/layout.tsx +++ b/web/app/account/(commonLayout)/layout.tsx @@ -1,7 +1,6 @@ import type { ReactNode } from 'react' import * as React from 'react' import { AppInitializer } from '@/app/components/app-initializer' -import AmplitudeProvider from '@/app/components/base/amplitude' import GA, { GaType } from '@/app/components/base/ga' import HeaderWrapper from '@/app/components/header/header-wrapper' import { AppContextProvider } from '@/context/app-context-provider' @@ -14,7 +13,6 @@ const Layout = ({ children }: { children: ReactNode }) => { return ( <> - diff --git a/web/app/components/base/amplitude/AmplitudeProvider.tsx b/web/app/components/base/amplitude/AmplitudeProvider.tsx index 00af15e24d..346cfaa7c4 100644 --- a/web/app/components/base/amplitude/AmplitudeProvider.tsx +++ b/web/app/components/base/amplitude/AmplitudeProvider.tsx @@ -1,82 +1,21 @@ 'use client' import type { FC } from 'react' -import * as amplitude from '@amplitude/analytics-browser' -import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser' +import type { AmplitudeInitializationOptions } from './init' import * as React from 'react' import { useEffect } from 'react' -import { AMPLITUDE_API_KEY, isAmplitudeEnabled } from '@/config' +import { ensureAmplitudeInitialized } from './init' -export type IAmplitudeProps = { - sessionReplaySampleRate?: number -} - -// Map URL pathname to English page name for consistent Amplitude tracking -const getEnglishPageName = (pathname: string): string => { - // Remove leading slash and get the first segment - const segments = pathname.replace(/^\//, '').split('/') - const firstSegment = segments[0] || 'home' - - const pageNameMap: Record = { - '': 'Home', - 'apps': 'Studio', - 'datasets': 'Knowledge', - 'explore': 'Explore', - 'tools': 'Tools', - 'account': 'Account', - 'signin': 'Sign In', - 'signup': 'Sign Up', - } - - return pageNameMap[firstSegment] || firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1) -} - -// Enrichment plugin to override page title with English name for page view events -const pageNameEnrichmentPlugin = (): amplitude.Types.EnrichmentPlugin => { - return { - name: 'page-name-enrichment', - type: 'enrichment', - setup: async () => undefined, - execute: async (event: amplitude.Types.Event) => { - // Only modify page view events - if (event.event_type === '[Amplitude] Page Viewed' && event.event_properties) { - /* v8 ignore next @preserve */ - const pathname = typeof window !== 'undefined' ? window.location.pathname : '' - event.event_properties['[Amplitude] Page Title'] = getEnglishPageName(pathname) - } - return event - }, - } -} +export type IAmplitudeProps = AmplitudeInitializationOptions const AmplitudeProvider: FC = ({ sessionReplaySampleRate = 0.5, }) => { useEffect(() => { - // Only enable in Saas edition with valid API key - if (!isAmplitudeEnabled) - return - - // Initialize Amplitude - amplitude.init(AMPLITUDE_API_KEY, { - defaultTracking: { - sessions: true, - pageViews: true, - formInteractions: true, - fileDownloads: true, - attribution: true, - }, + ensureAmplitudeInitialized({ + sessionReplaySampleRate, }) - - // Add page name enrichment plugin to override page title with English name - amplitude.add(pageNameEnrichmentPlugin()) - - // Add Session Replay plugin - const sessionReplay = sessionReplayPlugin({ - sampleRate: sessionReplaySampleRate, - }) - amplitude.add(sessionReplay) - }, []) + }, [sessionReplaySampleRate]) // This is a client component that renders nothing return null diff --git a/web/app/components/base/amplitude/__tests__/AmplitudeProvider.spec.tsx b/web/app/components/base/amplitude/__tests__/AmplitudeProvider.spec.tsx index 5835634eb7..a0080a0c0c 100644 --- a/web/app/components/base/amplitude/__tests__/AmplitudeProvider.spec.tsx +++ b/web/app/components/base/amplitude/__tests__/AmplitudeProvider.spec.tsx @@ -3,6 +3,7 @@ import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser' import { render } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import AmplitudeProvider from '../AmplitudeProvider' +import { resetAmplitudeInitializationForTests } from '../init' const mockConfig = vi.hoisted(() => ({ AMPLITUDE_API_KEY: 'test-api-key', @@ -35,6 +36,7 @@ describe('AmplitudeProvider', () => { vi.clearAllMocks() mockConfig.AMPLITUDE_API_KEY = 'test-api-key' mockConfig.IS_CLOUD_EDITION = true + resetAmplitudeInitializationForTests() }) describe('Component', () => { @@ -46,6 +48,17 @@ describe('AmplitudeProvider', () => { expect(amplitude.add).toHaveBeenCalledTimes(2) }) + it('does not re-initialize amplitude on remount', () => { + const { unmount } = render() + + unmount() + render() + + expect(amplitude.init).toHaveBeenCalledTimes(1) + expect(sessionReplayPlugin).toHaveBeenCalledTimes(1) + expect(amplitude.add).toHaveBeenCalledTimes(2) + }) + it('does not initialize amplitude when disabled', () => { mockConfig.AMPLITUDE_API_KEY = '' render() diff --git a/web/app/components/base/amplitude/__tests__/init.spec.ts b/web/app/components/base/amplitude/__tests__/init.spec.ts new file mode 100644 index 0000000000..25a5410148 --- /dev/null +++ b/web/app/components/base/amplitude/__tests__/init.spec.ts @@ -0,0 +1,61 @@ +import * as amplitude from '@amplitude/analytics-browser' +import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { ensureAmplitudeInitialized, resetAmplitudeInitializationForTests } from '../init' + +const mockConfig = vi.hoisted(() => ({ + AMPLITUDE_API_KEY: 'test-api-key', + IS_CLOUD_EDITION: true, +})) + +vi.mock('@/config', () => ({ + get AMPLITUDE_API_KEY() { + return mockConfig.AMPLITUDE_API_KEY + }, + get IS_CLOUD_EDITION() { + return mockConfig.IS_CLOUD_EDITION + }, + get isAmplitudeEnabled() { + return mockConfig.IS_CLOUD_EDITION && !!mockConfig.AMPLITUDE_API_KEY + }, +})) + +vi.mock('@amplitude/analytics-browser', () => ({ + init: vi.fn(), + add: vi.fn(), +})) + +vi.mock('@amplitude/plugin-session-replay-browser', () => ({ + sessionReplayPlugin: vi.fn(() => ({ name: 'session-replay' })), +})) + +describe('amplitude init helper', () => { + beforeEach(() => { + vi.clearAllMocks() + mockConfig.AMPLITUDE_API_KEY = 'test-api-key' + mockConfig.IS_CLOUD_EDITION = true + resetAmplitudeInitializationForTests() + }) + + describe('ensureAmplitudeInitialized', () => { + it('should initialize amplitude only once across repeated calls', () => { + ensureAmplitudeInitialized({ sessionReplaySampleRate: 0.8 }) + ensureAmplitudeInitialized({ sessionReplaySampleRate: 0.2 }) + + expect(amplitude.init).toHaveBeenCalledTimes(1) + expect(sessionReplayPlugin).toHaveBeenCalledTimes(1) + expect(sessionReplayPlugin).toHaveBeenCalledWith({ sampleRate: 0.8 }) + expect(amplitude.add).toHaveBeenCalledTimes(2) + }) + + it('should skip initialization when amplitude is disabled', () => { + mockConfig.AMPLITUDE_API_KEY = '' + + ensureAmplitudeInitialized() + + expect(amplitude.init).not.toHaveBeenCalled() + expect(sessionReplayPlugin).not.toHaveBeenCalled() + expect(amplitude.add).not.toHaveBeenCalled() + }) + }) +}) diff --git a/web/app/components/base/amplitude/init.ts b/web/app/components/base/amplitude/init.ts new file mode 100644 index 0000000000..209b7dfac0 --- /dev/null +++ b/web/app/components/base/amplitude/init.ts @@ -0,0 +1,82 @@ +import * as amplitude from '@amplitude/analytics-browser' +import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser' +import { AMPLITUDE_API_KEY, isAmplitudeEnabled } from '@/config' + +export type AmplitudeInitializationOptions = { + sessionReplaySampleRate?: number +} + +let isAmplitudeInitialized = false + +// Map URL pathname to English page name for consistent Amplitude tracking +const getEnglishPageName = (pathname: string): string => { + // Remove leading slash and get the first segment + const segments = pathname.replace(/^\//, '').split('/') + const firstSegment = segments[0] || 'home' + + const pageNameMap: Record = { + '': 'Home', + 'apps': 'Studio', + 'datasets': 'Knowledge', + 'explore': 'Explore', + 'tools': 'Tools', + 'account': 'Account', + 'signin': 'Sign In', + 'signup': 'Sign Up', + } + + return pageNameMap[firstSegment] || firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1) +} + +// Enrichment plugin to override page title with English name for page view events +const createPageNameEnrichmentPlugin = (): amplitude.Types.EnrichmentPlugin => { + return { + name: 'page-name-enrichment', + type: 'enrichment', + setup: async () => undefined, + execute: async (event: amplitude.Types.Event) => { + // Only modify page view events + if (event.event_type === '[Amplitude] Page Viewed' && event.event_properties) { + /* v8 ignore next @preserve */ + const pathname = typeof window !== 'undefined' ? window.location.pathname : '' + event.event_properties['[Amplitude] Page Title'] = getEnglishPageName(pathname) + } + return event + }, + } +} + +export const ensureAmplitudeInitialized = ({ + sessionReplaySampleRate = 0.5, +}: AmplitudeInitializationOptions = {}) => { + if (!isAmplitudeEnabled || isAmplitudeInitialized) + return + + isAmplitudeInitialized = true + + try { + amplitude.init(AMPLITUDE_API_KEY, { + defaultTracking: { + sessions: true, + pageViews: true, + formInteractions: true, + fileDownloads: true, + attribution: true, + }, + }) + + amplitude.add(createPageNameEnrichmentPlugin()) + amplitude.add(sessionReplayPlugin({ + sampleRate: sessionReplaySampleRate, + })) + } + catch (error) { + isAmplitudeInitialized = false + throw error + } +} + +// Only used by unit tests to reset module-scoped initialization state. +export const resetAmplitudeInitializationForTests = () => { + isAmplitudeInitialized = false +} diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 009f2ca584..9fb2b8dae2 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -4,6 +4,7 @@ import { TooltipProvider } from '@langgenius/dify-ui/tooltip' import { Provider as JotaiProvider } from 'jotai/react' import { ThemeProvider } from 'next-themes' import { NuqsAdapter } from 'nuqs/adapters/next/app' +import AmplitudeProvider from '@/app/components/base/amplitude' import { TanstackQueryInitializer } from '@/context/query-client' import { getDatasetMap } from '@/env' import { getLocaleOnServer } from '@/i18n-config/server' @@ -56,6 +57,7 @@ const LocaleLayout = async ({ {...datasetMap} >
+ Date: Mon, 20 Apr 2026 13:22:52 +0530 Subject: [PATCH 003/253] fix: handle numpy scalar types in safe_json_value (#35389) Co-authored-by: Abhay --- api/core/tools/utils/message_transformer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/core/tools/utils/message_transformer.py b/api/core/tools/utils/message_transformer.py index 79d0c114d4..5679466cbc 100644 --- a/api/core/tools/utils/message_transformer.py +++ b/api/core/tools/utils/message_transformer.py @@ -41,6 +41,10 @@ def safe_json_value(v): return v.hex() elif isinstance(v, memoryview): return v.tobytes().hex() + elif isinstance(v, np.integer): + return int(v) + elif isinstance(v, np.floating): + return float(v) elif isinstance(v, np.ndarray): return v.tolist() elif isinstance(v, dict): From 4f03b7193e5936fd12f3cfdb86326759b2ca7aa2 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:54:49 +0800 Subject: [PATCH 004/253] docs(frontend): align docs and comments (#35364) --- .agents/skills/frontend-testing/SKILL.md | 6 +- .../frontend-testing/references/checklist.md | 4 +- .../frontend-testing/references/mocking.md | 26 ++--- packages/dify-ui/README.md | 105 ++++++++++++++++++ packages/dify-ui/src/dialog/index.tsx | 2 +- .../dify-ui/src/scroll-area/index.stories.tsx | 2 +- .../src/toast/__tests__/index.spec.tsx | 2 +- web/AGENTS.md | 5 +- web/README.md | 2 +- .../base/portal-to-follow-elem/index.tsx | 2 +- .../__tests__/index.spec.tsx | 2 +- .../workflow-onboarding-modal/index.tsx | 2 +- web/app/components/workflow/run/node.tsx | 36 ++---- web/docs/overlay-migration.md | 34 +++--- web/docs/test.md | 6 +- web/eslint.config.mjs | 7 -- web/eslint.constants.mjs | 2 +- web/scripts/analyze-component.js | 6 +- 18 files changed, 166 insertions(+), 85 deletions(-) create mode 100644 packages/dify-ui/README.md diff --git a/.agents/skills/frontend-testing/SKILL.md b/.agents/skills/frontend-testing/SKILL.md index 4da070bdbf..105c979c58 100644 --- a/.agents/skills/frontend-testing/SKILL.md +++ b/.agents/skills/frontend-testing/SKILL.md @@ -200,7 +200,7 @@ When assigned to test a directory/path, test **ALL content** within that path: - ✅ **Import real project components** directly (including base components and siblings) - ✅ **Only mock**: API services (`@/service/*`), `next/navigation`, complex context providers -- ❌ **DO NOT mock** base components (`@/app/components/base/*`) +- ❌ **DO NOT mock** base components (`@/app/components/base/*`) or dify-ui primitives (`@langgenius/dify-ui/*`) - ❌ **DO NOT mock** sibling/child components in the same directory > See [Test Structure Template](#test-structure-template) for correct import/mock patterns. @@ -325,12 +325,12 @@ For more detailed information, refer to: ### Reference Examples in Codebase - `web/utils/classnames.spec.ts` - Utility function tests -- `web/app/components/base/button/index.spec.tsx` - Component tests +- `web/app/components/base/radio/__tests__/index.spec.tsx` - Component tests - `web/__mocks__/provider-context.ts` - Mock factory example ### Project Configuration -- `web/vitest.config.ts` - Vitest configuration +- `web/vite.config.ts` - Vite/Vitest configuration - `web/vitest.setup.ts` - Test environment setup - `web/scripts/analyze-component.js` - Component analysis tool - Modules are not mocked automatically. Global mocks live in `web/vitest.setup.ts` (for example `react-i18next`, `next/image`); mock other modules like `ky` or `mime` locally in test files. diff --git a/.agents/skills/frontend-testing/references/checklist.md b/.agents/skills/frontend-testing/references/checklist.md index 10b8fb66f9..99258498dd 100644 --- a/.agents/skills/frontend-testing/references/checklist.md +++ b/.agents/skills/frontend-testing/references/checklist.md @@ -36,7 +36,7 @@ Use this checklist when generating or reviewing tests for Dify frontend componen ### Integration vs Mocking -- [ ] **DO NOT mock base components** (`Loading`, `Button`, `Tooltip`, etc.) +- [ ] **DO NOT mock base components or dify-ui primitives** (base `Loading`, `Input`, `Badge`; dify-ui `Button`, `Tooltip`, `Dialog`, etc.) - [ ] Import real project components instead of mocking - [ ] Only mock: API calls, complex context providers, third-party libs with side effects - [ ] Prefer integration testing when using single spec file @@ -73,7 +73,7 @@ Use this checklist when generating or reviewing tests for Dify frontend componen ### Mocks -- [ ] **DO NOT mock base components** (`@/app/components/base/*`) +- [ ] **DO NOT mock base components or dify-ui primitives** (`@/app/components/base/*` or `@langgenius/dify-ui/*`) - [ ] `vi.clearAllMocks()` in `beforeEach` (not `afterEach`) - [ ] Shared mock state reset in `beforeEach` - [ ] i18n uses global mock (auto-loaded in `web/vitest.setup.ts`); only override locally for custom translations diff --git a/.agents/skills/frontend-testing/references/mocking.md b/.agents/skills/frontend-testing/references/mocking.md index f58377c4a5..8c2f1c0c58 100644 --- a/.agents/skills/frontend-testing/references/mocking.md +++ b/.agents/skills/frontend-testing/references/mocking.md @@ -2,29 +2,27 @@ ## ⚠️ Important: What NOT to Mock -### DO NOT Mock Base Components +### DO NOT Mock Base Components or dify-ui Primitives -**Never mock components from `@/app/components/base/`** such as: +**Never mock components from `@/app/components/base/` or from `@langgenius/dify-ui/*`** such as: -- `Loading`, `Spinner` -- `Button`, `Input`, `Select` -- `Tooltip`, `Modal`, `Dropdown` -- `Icon`, `Badge`, `Tag` +- Legacy base (`@/app/components/base/*`): `Loading`, `Spinner`, `Input`, `Badge`, `Tag` +- dify-ui primitives (`@langgenius/dify-ui/*`): `Button`, `Tooltip`, `Dialog`, `Popover`, `DropdownMenu`, `ContextMenu`, `Select`, `AlertDialog`, `Toast` **Why?** -- Base components will have their own dedicated tests +- These components have their own dedicated tests - Mocking them creates false positives (tests pass but real integration fails) - Using real components tests actual integration behavior ```typescript -// ❌ WRONG: Don't mock base components +// ❌ WRONG: Don't mock base components or dify-ui primitives vi.mock('@/app/components/base/loading', () => () =>
Loading
) -vi.mock('@/app/components/base/button', () => ({ children }: any) => ) +vi.mock('@langgenius/dify-ui/button', () => ({ Button: ({ children }: any) => })) -// ✅ CORRECT: Import and use real base components +// ✅ CORRECT: Import and use the real components import Loading from '@/app/components/base/loading' -import Button from '@/app/components/base/button' +import { Button } from '@langgenius/dify-ui/button' // They will render normally in tests ``` @@ -319,7 +317,7 @@ const renderWithQueryClient = (ui: React.ReactElement) => { ### ✅ DO -1. **Use real base components** - Import from `@/app/components/base/` directly +1. **Use real base components and dify-ui primitives** - Import from `@/app/components/base/` or `@langgenius/dify-ui/*` directly 1. **Use real project components** - Prefer importing over mocking 1. **Use real Zustand stores** - Set test state via `store.setState()` 1. **Reset mocks in `beforeEach`**, not `afterEach` @@ -330,7 +328,7 @@ const renderWithQueryClient = (ui: React.ReactElement) => { ### ❌ DON'T -1. **Don't mock base components** (`Loading`, `Button`, `Tooltip`, etc.) +1. **Don't mock base components or dify-ui primitives** (`Loading`, `Input`, `Button`, `Tooltip`, `Dialog`, etc.) 1. **Don't mock Zustand store modules** - Use real stores with `setState()` 1. Don't mock components you can import directly 1. Don't create overly simplified mocks that miss conditional logic @@ -342,7 +340,7 @@ const renderWithQueryClient = (ui: React.ReactElement) => { ``` Need to use a component in test? │ -├─ Is it from @/app/components/base/*? +├─ Is it from @/app/components/base/* or @langgenius/dify-ui/*? │ └─ YES → Import real component, DO NOT mock │ ├─ Is it a project component? diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md new file mode 100644 index 0000000000..5e4e439e5f --- /dev/null +++ b/packages/dify-ui/README.md @@ -0,0 +1,105 @@ +# @langgenius/dify-ui + +Shared UI primitives, design tokens, Tailwind preset, and the `cn()` utility consumed by Dify's `web/` app. + +The primitives are thin, opinionated wrappers around [Base UI] headless components, styled with `cva` + `cn` and Dify design tokens. + +> `private: true` — this package is consumed by `web/` via the pnpm workspace and is not published to npm. Treat the API as internal to Dify, but stable within the workspace. + +## Installation + +Already wired as a workspace dependency in `web/package.json`. Nothing to install. + +For a new workspace consumer, add: + +```jsonc +{ + "dependencies": { + "@langgenius/dify-ui": "workspace:*" + } +} +``` + +## Imports + +Always import from a **subpath export** — there is no barrel: + +```ts +import { Button } from '@langgenius/dify-ui/button' +import { cn } from '@langgenius/dify-ui/cn' +import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +import '@langgenius/dify-ui/styles.css' // once, in the app root +``` + +Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported — it keeps tree-shaking trivial and makes Storybook / test coverage attribution per-primitive. + +## Primitives + +| Category | Subpath | Notes | +| -------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | +| Overlay | `./alert-dialog`, `./context-menu`, `./dialog`, `./dropdown-menu`, `./popover`, `./select`, `./toast`, `./tooltip` | Portalled. See [Overlay & portal contract] below. | +| Form | `./number-field`, `./slider`, `./switch` | Controlled / uncontrolled per Base UI defaults. | +| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. | +| Media | `./avatar`, `./button` | Button exposes `cva` variants. | + +Utilities: + +- `./cn` — `clsx` + `tailwind-merge` wrapper. Use this for conditional class composition. +- `./tailwind-preset` — Tailwind v4 preset with Dify tokens. Apps extend it from their own `tailwind.config.ts`. +- `./styles.css` — the one CSS entry that ships the design tokens, theme variables, and base reset. Import it once from the app root. + +## Overlay & portal contract + +All overlay primitives (`dialog`, `alert-dialog`, `popover`, `dropdown-menu`, `context-menu`, `select`, `tooltip`, `toast`) render their content inside a [Base UI Portal] attached to `document.body`. This is the Base UI default — see the upstream [Portals][Base UI Portal] docs for the underlying behavior. Consumers **do not** need to wrap anything in a portal manually. + +### Root isolation requirement + +The host app **must** establish an isolated stacking context at its root so the portalled overlay layer is not clipped or re-ordered by ancestor `transform` / `filter` / `contain` styles. In the Dify web app this is done in `web/app/layout.tsx`: + +```tsx + +
{children}
+ +``` + +Equivalent: any root element with `isolation: isolate` in CSS. Without it, overlays can be visually clipped on Safari when a descendant creates a new stacking context. + +### z-index layering + +Every overlay primitive uses a single, shared z-index. Do **not** override it at call sites. + +| Layer | z-index | Where | +| ----------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------- | +| Overlays (Dialog, AlertDialog, Popover, DropdownMenu, ContextMenu, Select, Tooltip) | `z-1002` | Positioner / Backdrop | +| Toast viewport | `z-1003` | One layer above overlays so notifications are never hidden under a dialog. | + +Rationale: during Dify's migration from legacy `portal-to-follow-elem` / `base/modal` / `base/dialog` overlays to this package, new and old overlays coexist in the DOM. `z-1002` sits above any common legacy layer, eliminating per-call-site z-index hacks. Among themselves, new primitives share the same z-index and **rely on DOM order** for stacking — the portal mounted later wins. + +See `[web/docs/overlay-migration.md](../../web/docs/overlay-migration.md)` for the Dify-web migration history and the remaining legacy allowlist. Once the legacy overlays are gone, the values in this table can drop back to `z-50` / `z-51`. + +### Rules + +- Never add `z-1003` / `z-9999` / etc. overrides on primitives from this package. If something is getting clipped, the **parent** overlay (typically a legacy one) is the problem and should be migrated. +- Never portal an overlay manually on top of our primitives — use `DialogTrigger`, `PopoverTrigger`, etc. Base UI handles focus management, scroll-locking, and dismissal. +- When a primitive needs additional presentation chrome (e.g. a custom backdrop), add it **inside** the exported component, not at call sites. + +## Development + +- `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives. +- `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`. +- `pnpm -C packages/dify-ui type-check` — `tsc --noEmit` for this package only. + +See `[AGENTS.md](./AGENTS.md)` for: + +- Component authoring rules (one-component-per-folder, `cva` + `cn`, relative imports inside the package, subpath imports from consumers). +- Figma `--radius/`* token → Tailwind `rounded-*` class mapping. + +## Not part of this package + +- Application state (`jotai`, `zustand`), data fetching (`ky`, `@tanstack/react-query`, `@orpc/*`), i18n (`next-i18next` / `react-i18next`), and routing (`next`) all live in `web/`. This package has zero dependencies on them and must stay that way so it can eventually be consumed by other apps or extracted. +- Business components (chat, workflow, dataset views, etc.). Those belong in `web/app/components/...`. + +[Base UI Portal]: https://base-ui.com/react/overview/quick-start#portals +[Base UI]: https://base-ui.com/react +[Overlay & portal contract]: #overlay--portal-contract diff --git a/packages/dify-ui/src/dialog/index.tsx b/packages/dify-ui/src/dialog/index.tsx index 517d6cc9fc..24c5bcc463 100644 --- a/packages/dify-ui/src/dialog/index.tsx +++ b/packages/dify-ui/src/dialog/index.tsx @@ -1,7 +1,7 @@ 'use client' // z-index strategy (relies on root `isolation: isolate` in layout.tsx): -// All base/ui/* overlay primitives — z-1002 +// All @langgenius/dify-ui/* overlay primitives — z-1002 // Toast stays one layer above overlays at z-1003. // Overlays share the same z-index; DOM order handles stacking when multiple are open. // This ensures overlays inside a Dialog (e.g. a Tooltip on a dialog button) render diff --git a/packages/dify-ui/src/scroll-area/index.stories.tsx b/packages/dify-ui/src/scroll-area/index.stories.tsx index e1f8f9cfb5..433817948f 100644 --- a/packages/dify-ui/src/scroll-area/index.stories.tsx +++ b/packages/dify-ui/src/scroll-area/index.stories.tsx @@ -174,7 +174,7 @@ const StickyListPane = () => (
Operational queue
-

The scrollbar is still the shared base/ui primitive, while the pane adds sticky structure and a viewport mask.

+

The scrollbar is still the shared dify-ui primitive, while the pane adds sticky structure and a viewport mask.

24 items diff --git a/packages/dify-ui/src/toast/__tests__/index.spec.tsx b/packages/dify-ui/src/toast/__tests__/index.spec.tsx index 8bdf4417e0..edbdacd203 100644 --- a/packages/dify-ui/src/toast/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/toast/__tests__/index.spec.tsx @@ -8,7 +8,7 @@ declare global { var BASE_UI_ANIMATIONS_DISABLED: boolean | undefined } -describe('base/ui/toast', () => { +describe('@langgenius/dify-ui/toast', () => { beforeAll(() => { // Base UI waits for `requestAnimationFrame` + `getAnimations().finished` // before unmounting a toast. Fake timers can't reliably drive that path, diff --git a/web/AGENTS.md b/web/AGENTS.md index 4a705bf4b8..5e9f7ed11c 100644 --- a/web/AGENTS.md +++ b/web/AGENTS.md @@ -4,8 +4,9 @@ ## Overlay Components (Mandatory) -- `./docs/overlay-migration.md` is the source of truth for overlay-related work. -- In new or modified code, use only overlay primitives from `@/app/components/base/ui/*`. +- `../packages/dify-ui/README.md` is the permanent contract for overlay primitives, portals, root `isolation: isolate`, and the `z-1002` / `z-1003` layering. +- `./docs/overlay-migration.md` is the source of truth for the ongoing migration (deprecated import paths, allowlist, coexistence rules). +- In new or modified code, use only overlay primitives from `@langgenius/dify-ui/*`. - Do not introduce deprecated overlay imports from `@/app/components/base/*`; when touching legacy callers, prefer migrating them and keep the allowlist shrinking (never expanding). ## Query & Mutation (Mandatory) diff --git a/web/README.md b/web/README.md index 683a18c769..eb964b01e3 100644 --- a/web/README.md +++ b/web/README.md @@ -165,7 +165,7 @@ The Dify community can be found on [Discord community], where you can ask questi [Storybook]: https://storybook.js.org [Vite+]: https://viteplus.dev [Vitest]: https://vitest.dev -[index.spec.tsx]: ./app/components/base/button/index.spec.tsx +[index.spec.tsx]: ./app/components/base/radio/__tests__/index.spec.tsx [pnpm]: https://pnpm.io [vinext]: https://github.com/cloudflare/vinext [web/docs/test.md]: ./docs/test.md diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx index 48099c7020..8b531be309 100644 --- a/web/app/components/base/portal-to-follow-elem/index.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.tsx @@ -1,6 +1,6 @@ 'use client' /** - * @deprecated Use semantic overlay primitives from `@/app/components/base/ui/` instead. + * @deprecated Use semantic overlay primitives from `@langgenius/dify-ui/*` instead. * This component will be removed after migration is complete. * See: https://github.com/langgenius/dify/issues/32767 * diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx index e2248a44c3..206a7c0148 100644 --- a/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx @@ -54,7 +54,7 @@ describe('EditWorkspaceModal', () => { expect(await screen.findByDisplayValue('Test Workspace')).toBeInTheDocument() }) - it('should render on the base/ui overlay layer', async () => { + it('should render on the dify-ui overlay layer', async () => { renderModal() expect(await screen.findByRole('dialog')).toHaveClass('z-1002') diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx index cbb875a758..c72a515925 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx @@ -45,7 +45,7 @@ const WorkflowOnboardingModal: FC = ({
- {/* TODO: reduce z-1002 to match base/ui primitives after legacy overlay migration completes */} + {/* TODO: reduce z-1002 to match @langgenius/dify-ui primitives after legacy overlay migration completes */}
{t('onboarding.escTip.press', { ns: 'workflow' })} diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index b683467d5f..3f7e9e9999 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -69,7 +69,15 @@ const NodePanel: FC = ({ doSetCollapseState(state) }, [hideProcessDetail]) const titleRef = useRef(null) - const [isTitleTruncated, setIsTitleTruncated] = useState(false) + const [isTooltipOpen, setIsTooltipOpen] = useState(false) + const handleTooltipOpenChange = useCallback((open: boolean) => { + if (open) { + const el = titleRef.current + if (!el || el.scrollWidth <= el.clientWidth) + return + } + setIsTooltipOpen(open) + }, []) const { t } = useTranslation() const docLink = useDocLink() @@ -94,27 +102,6 @@ const NodePanel: FC = ({ setCollapseState(!nodeInfo.expand) }, [nodeInfo.expand, setCollapseState]) - useEffect(() => { - const titleElement = titleRef.current - if (!titleElement) - return - - let frameId = 0 - const updateIsTitleTruncated = () => { - setIsTitleTruncated(titleElement.scrollWidth > titleElement.clientWidth) - } - - frameId = requestAnimationFrame(updateIsTitleTruncated) - - const resizeObserver = new ResizeObserver(updateIsTitleTruncated) - resizeObserver.observe(titleElement) - - return () => { - cancelAnimationFrame(frameId) - resizeObserver.disconnect() - } - }, [nodeInfo.title]) - const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration && !!nodeInfo.details?.length const isLoopNode = nodeInfo.node_type === BlockEnum.Loop && !!nodeInfo.details?.length const isRetryNode = hasRetryNode(nodeInfo.node_type) && !!nodeInfo.retryDetail?.length @@ -155,14 +142,13 @@ const NodePanel: FC = ({ /> )} - + diff --git a/web/docs/overlay-migration.md b/web/docs/overlay-migration.md index 0016f34e12..0fa584ecfc 100644 --- a/web/docs/overlay-migration.md +++ b/web/docs/overlay-migration.md @@ -1,6 +1,8 @@ # Overlay Migration Guide -This document tracks the migration away from legacy overlay APIs. +This document tracks the Dify-web migration away from legacy overlay APIs. + +> **See also:** [`packages/dify-ui/README.md`] for the permanent overlay / portal / z-index contract of the replacement primitives. This document covers the one-off migration mechanics (allowlist, deprecated import paths, coexistence z-index strategy) and is expected to shrink and eventually be removed once the legacy overlays are gone. ## Scope @@ -31,7 +33,7 @@ This document tracks the migration away from legacy overlay APIs. ## Migration phases 1. Business/UI features outside `app/components/base/**` - - Migrate old calls to semantic primitives from `@/app/components/base/ui/**`. + - Migrate old calls to semantic primitives from `@langgenius/dify-ui/*`. - Keep deprecated imports out of newly touched files. 1. Legacy base components in allowlist - Migrate allowlisted base callers gradually. @@ -53,7 +55,7 @@ pnpm -C web lint:fix --prune-suppressions ## z-index strategy -All new overlay primitives in `base/ui/` share a single z-index value: +All new overlay primitives in `@langgenius/dify-ui` share a single z-index value: **`z-1002`**, except Toast which stays one layer above at **`z-1003`**. ### Why z-[1002]? @@ -61,13 +63,13 @@ All new overlay primitives in `base/ui/` share a single z-index value: During the migration period, legacy and new overlays coexist. Legacy overlays portal to `document.body` with explicit z-index values: -| Layer | z-index | Components | -| --------------------------------- | -------------- | -------------------------------------------- | -| Legacy Drawer | `z-30` | `base/drawer` | -| Legacy Modal | `z-60` | `base/modal` (default) | -| Legacy PortalToFollowElem callers | up to `z-1001` | various business components | -| **New UI primitives** | **`z-1002`** | `base/ui/*` (Popover, Dialog, Tooltip, etc.) | -| Toast | `z-1003` | `base/ui/toast` | +| Layer | z-index | Components | +| --------------------------------- | -------------- | -------------------------------------------------------- | +| Legacy Drawer | `z-30` | `base/drawer` | +| Legacy Modal | `z-60` | `base/modal` (default) | +| Legacy PortalToFollowElem callers | up to `z-1001` | various business components | +| **New UI primitives** | **`z-1002`** | `@langgenius/dify-ui/*` (Popover, Dialog, Tooltip, etc.) | +| Toast | `z-1003` | `@langgenius/dify-ui/toast` | `z-1002` sits above all common legacy overlays, so new primitives always render on top without needing per-call-site z-index hacks. Among themselves, @@ -81,8 +83,8 @@ back to `z-9999`. ### Rules - **Do NOT add z-index overrides** (e.g. `className="z-1003"`) on new - `base/ui/*` components. If you find yourself needing one, the parent legacy - overlay should be migrated instead. + `@langgenius/dify-ui/*` components. If you find yourself needing one, the + parent legacy overlay should be migrated instead. - When migrating a legacy overlay that has a high z-index, remove the z-index entirely — the new primitive's default `z-1002` handles it. - `portalToFollowElemContentClassName` with z-index values (e.g. `z-1000`) @@ -92,12 +94,8 @@ back to `z-9999`. Once all legacy overlays are removed: -1. Reduce `z-1002` back to `z-50` across all `base/ui/` primitives. +1. Reduce `z-1002` back to `z-50` across all `@langgenius/dify-ui` primitives. 1. Reduce Toast from `z-1003` to `z-51`. 1. Remove this section from the migration guide. -## React Refresh policy for base UI primitives - -- We keep primitive aliases (for example `DropdownMenu = Menu.Root`) in the same module. -- For `app/components/base/ui/**/*.tsx`, `react-refresh/only-export-components` is currently set to `off` in ESLint to avoid false positives and IDE noise during migration. -- Do not use file-level `eslint-disable` comments for this policy; keep control in the scoped ESLint override. +[`packages/dify-ui/README.md`]: ../../packages/dify-ui/README.md diff --git a/web/docs/test.md b/web/docs/test.md index 58acccec2a..836ab7ad56 100644 --- a/web/docs/test.md +++ b/web/docs/test.md @@ -95,7 +95,7 @@ Use `pnpm analyze-component ` to analyze component complexity and adopt di - Testing time-based behavior (delays, animations) - If you mock all time-dependent functions, fake timers are unnecessary 1. **Prefer importing over mocking project components**: When tests need other components from the project, import them directly instead of mocking them. Only mock external dependencies, APIs, or complex context providers that are difficult to set up. -1. **DO NOT mock base components**: Never mock components from `@/app/components/base/` (e.g., `Loading`, `Button`, `Tooltip`, `Modal`). Base components will have their own dedicated tests. Use real components to test actual integration behavior. +1. **DO NOT mock base components or dify-ui primitives**: Never mock components from `@/app/components/base/` (e.g., `Loading`, `Input`, `Badge`, `Tag`) or from `@langgenius/dify-ui/*` (e.g., `Button`, `Tooltip`, `Dialog`, `Select`, `Popover`). They have their own dedicated tests. Use real components to test actual integration behavior. **Why this matters**: Mocks that don't match actual behavior can lead to: @@ -134,7 +134,7 @@ When using a single spec file: - ✅ **Import real project components** directly (including base components and siblings) - ✅ **Only mock**: API services (`@/service/*`), `next/navigation`, complex context providers -- ❌ **DO NOT mock** base components (`@/app/components/base/*`) +- ❌ **DO NOT mock** base components (`@/app/components/base/*`) or dify-ui primitives (`@langgenius/dify-ui/*`) - ❌ **DO NOT mock** sibling/child components in the same directory > See [Example Structure] for correct import/mock patterns. @@ -539,4 +539,4 @@ Test examples in the project: [Testing Library Best Practices]: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library [Vitest Documentation]: https://vitest.dev/guide [Vitest Mocking Guide]: https://vitest.dev/guide/mocking.html -[index.spec.tsx]: ../app/components/base/button/index.spec.tsx +[index.spec.tsx]: ../app/components/base/radio/__tests__/index.spec.tsx diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index eab02ec664..62cc63536f 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -160,13 +160,6 @@ export default antfu( 'hyoban/no-dependency-version-prefix': 'error', }, }, - { - name: 'dify/base-ui-primitives', - files: ['app/components/base/ui/**/*.tsx'], - rules: { - 'react-refresh/only-export-components': 'off', - }, - }, { name: 'dify/no-direct-next-imports', files: [GLOB_TS, GLOB_TSX], diff --git a/web/eslint.constants.mjs b/web/eslint.constants.mjs index 46690035fb..5a2330c00e 100644 --- a/web/eslint.constants.mjs +++ b/web/eslint.constants.mjs @@ -26,7 +26,7 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [ '**/portal-to-follow-elem', '**/portal-to-follow-elem/index', ], - message: 'Deprecated: use semantic overlay primitives from @/app/components/base/ui/ instead. See issue #32767.', + message: 'Deprecated: use semantic overlay primitives from @langgenius/dify-ui (popover / dropdown-menu / tooltip / context-menu) instead. See issue #32767.', }, { group: [ diff --git a/web/scripts/analyze-component.js b/web/scripts/analyze-component.js index 04bf0d8f71..36f31f3d3d 100755 --- a/web/scripts/analyze-component.js +++ b/web/scripts/analyze-component.js @@ -374,13 +374,13 @@ Options: Examples: # Analyze a component and generate test prompt - pnpm analyze-component app/components/base/button/index.tsx + pnpm analyze-component app/components/base/radio/index.tsx # Output as JSON - pnpm analyze-component app/components/base/button/index.tsx --json + pnpm analyze-component app/components/base/radio/index.tsx --json # Review existing test - pnpm analyze-component app/components/base/button/index.tsx --review + pnpm analyze-component app/components/base/radio/index.tsx --review For complete testing guidelines, see: web/docs/test.md `) From 39dc636b02d8540e845cf85c6e8dd08b6a6c121c Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Mon, 20 Apr 2026 16:07:46 +0800 Subject: [PATCH 005/253] fix: missing icon from iconify set (#35420) --- .../custom-public/icons.json | 294 ++++----- .../custom-public/info.json | 2 +- .../custom-vender/icons.json | 556 +++++++++--------- packages/iconify-collections/package.json | 5 +- ...ollections.mjs => generate-collections.ts} | 161 +++-- pnpm-lock.yaml | 15 +- pnpm-workspace.yaml | 2 +- 7 files changed, 558 insertions(+), 477 deletions(-) rename packages/iconify-collections/scripts/{generate-collections.mjs => generate-collections.ts} (59%) diff --git a/packages/iconify-collections/custom-public/icons.json b/packages/iconify-collections/custom-public/icons.json index 347b6145e2..13d8400b89 100644 --- a/packages/iconify-collections/custom-public/icons.json +++ b/packages/iconify-collections/custom-public/icons.json @@ -1,571 +1,579 @@ { "prefix": "custom-public", - "lastModified": 1775115796, + "lastModified": 1776670621, "icons": { "avatar-user": { - "body": "", + "body": "", "width": 512, "height": 512 }, "billing-ar-cube-1": { - "body": "", + "body": "", "width": 28 }, "billing-asterisk": { - "body": "", + "body": "", "width": 28 }, "billing-aws-marketplace-dark": { - "body": "", + "body": "", "width": 126, "height": 25 }, "billing-aws-marketplace-light": { - "body": "", + "body": "", "width": 126, "height": 24 }, "billing-azure": { - "body": "", + "body": "", "width": 21, "height": 20 }, "billing-buildings": { - "body": "" + "body": "" }, "billing-diamond": { - "body": "" + "body": "" }, "billing-google-cloud": { - "body": "", + "body": "", "width": 22, "height": 18 }, "billing-group-2": { - "body": "" + "body": "" }, "billing-keyframe": { - "body": "" + "body": "" }, "billing-sparkles-soft": { - "body": "", + "body": "", "width": 13, "height": 13 }, "common-d": { - "body": "" + "body": "" }, "common-diagonal-dividing-line": { - "body": "", + "body": "", "width": 7, "height": 20 }, "common-dify": { - "body": "", + "body": "", "width": 50, "height": 26 }, + "common-enter-key": { + "body": "" + }, "common-gdpr": { - "body": "", + "body": "", "width": 23, "height": 28 }, "common-github": { - "body": "", + "body": "", "width": 18, "height": 18 }, "common-highlight": { - "body": "", + "body": "", "width": 46, "height": 24 }, "common-iso": { - "body": "", + "body": "", "width": 64, "height": 64 }, "common-line-3": { - "body": "", + "body": "", "width": 5, "height": 12 }, "common-lock": { - "body": "" + "body": "" }, "common-message-chat-square": { - "body": "" + "body": "" }, "common-multi-path-retrieval": { - "body": "", + "body": "", "width": 36, "height": 36 }, "common-n-to-1-retrieval": { - "body": "", + "body": "", "width": 36, "height": 36 }, "common-notion": { - "body": "", + "body": "", "width": 20, "height": 20 }, "common-soc2": { - "body": "", + "body": "", "width": 28, "height": 28 }, "common-sparkles-soft": { - "body": "", + "body": "", "width": 14, "height": 14 }, "common-sparkles-soft-accent": { - "body": "" + "body": "" }, "education-triangle": { - "body": "", + "body": "", "height": 22 }, "files-csv": { - "body": "" + "body": "" }, "files-doc": { - "body": "" + "body": "" }, "files-docx": { - "body": "" + "body": "" }, "files-html": { - "body": "" + "body": "" }, "files-json": { - "body": "" + "body": "" }, "files-md": { - "body": "" + "body": "" }, "files-pdf": { - "body": "" + "body": "" }, "files-txt": { - "body": "" + "body": "" }, "files-unknown": { - "body": "" + "body": "" }, "files-xlsx": { - "body": "", + "body": "", "width": 24, "height": 26 }, "files-yaml": { - "body": "", + "body": "", "width": 24, "height": 26 }, "knowledge-file": { - "body": "", + "body": "", "width": 16, "height": 16 }, "knowledge-option-card-effect-blue": { - "body": "", + "body": "", "width": 214, "height": 124 }, "knowledge-option-card-effect-blue-light": { - "body": "", + "body": "", "width": 212, "height": 74 }, "knowledge-option-card-effect-orange": { - "body": "" + "body": "" }, "knowledge-option-card-effect-purple": { - "body": "" + "body": "" }, "knowledge-option-card-effect-teal": { - "body": "", + "body": "", "width": 212, "height": 92 }, "knowledge-selection-mod": { - "body": "", + "body": "", "width": 10, "height": 10 }, "knowledge-watercrawl": { - "body": "", + "body": "", "width": 500, "height": 500 }, "knowledge-dataset-card-external-knowledge-base": { - "body": "" + "body": "" }, "knowledge-dataset-card-general": { - "body": "" + "body": "" }, "knowledge-dataset-card-graph": { - "body": "" + "body": "" }, "knowledge-dataset-card-parent-child": { - "body": "" + "body": "" }, "knowledge-dataset-card-qa": { - "body": "" + "body": "" }, "knowledge-online-drive-buckets-blue": { - "body": "", + "body": "", "height": 21 }, "knowledge-online-drive-buckets-gray": { - "body": "", + "body": "", "width": 18 }, "knowledge-online-drive-folder": { - "body": "" + "body": "" }, "llm-anthropic": { - "body": "" + "body": "" }, "llm-anthropic-dark": { - "body": "", + "body": "", "width": 90, "height": 10 }, "llm-anthropic-light": { - "body": "", + "body": "", "width": 90, "height": 10 }, "llm-anthropic-short-light": { - "body": "", + "body": "", "width": 40, "height": 40 }, "llm-anthropic-text": { - "body": "", + "body": "", "width": 90, "height": 20 }, "llm-azure-openai-service": { - "body": "", + "body": "", "width": 56 }, "llm-azure-openai-service-text": { - "body": "", + "body": "", "width": 212 }, "llm-azureai": { - "body": "" + "body": "" }, "llm-azureai-text": { - "body": "", + "body": "", "width": 92 }, "llm-baichuan": { - "body": "" + "body": "" }, "llm-baichuan-text": { - "body": "", + "body": "", "width": 130 }, "llm-chatglm": { - "body": "" + "body": "" }, "llm-chatglm-text": { - "body": "", + "body": "", "width": 100 }, "llm-cohere": { - "body": "", + "body": "", "width": 22, "height": 22 }, "llm-cohere-text": { - "body": "", + "body": "", "width": 120 }, "llm-deepseek": { - "body": "", + "body": "", "width": 40, "height": 40 }, "llm-gemini": { - "body": "", + "body": "", "width": 40, "height": 40 }, "llm-gpt-3": { - "body": "" + "body": "" }, "llm-gpt-4": { - "body": "" + "body": "" }, "llm-grok": { - "body": "", + "body": "", "width": 40, "height": 40 }, "llm-huggingface": { - "body": "" + "body": "" }, "llm-huggingface-text": { - "body": "", + "body": "", "width": 120 }, "llm-huggingface-text-hub": { - "body": "", + "body": "", "width": 151 }, "llm-iflytek-spark": { - "body": "" + "body": "" }, "llm-iflytek-spark-text": { - "body": "", + "body": "", "width": 150 }, "llm-iflytek-spark-text-cn": { - "body": "", + "body": "", "width": 84 }, "llm-jina": { - "body": "" + "body": "" }, "llm-jina-text": { - "body": "", + "body": "", "width": 58 }, "llm-microsoft": { - "body": "", + "body": "", "width": 21, "height": 22 }, "llm-openai-black": { - "body": "" + "body": "" }, "llm-openai-blue": { - "body": "" + "body": "" }, "llm-openai-green": { - "body": "" + "body": "" }, "llm-openai-teal": { - "body": "" + "body": "" }, "llm-openai-text": { - "body": "", + "body": "", "width": 52, "height": 20 }, "llm-openai-transparent": { - "body": "" + "body": "" }, "llm-openai-violet": { - "body": "" + "body": "" }, "llm-openai-yellow": { - "body": "" + "body": "" }, "llm-openllm": { - "body": "" + "body": "" }, "llm-openllm-text": { - "body": "", + "body": "", "width": 92, "height": 25 }, "llm-replicate": { - "body": "" + "body": "" }, "llm-replicate-text": { - "body": "", + "body": "", "width": 92 }, "llm-xorbits-inference": { - "body": "" + "body": "" }, "llm-xorbits-inference-text": { - "body": "", + "body": "", "width": 152 }, "llm-zhipuai": { - "body": "" + "body": "" }, "llm-zhipuai-text": { - "body": "", + "body": "", "width": 89, "height": 32 }, "llm-zhipuai-text-cn": { - "body": "", + "body": "", "width": 86, "height": 32 }, "model-checked": { - "body": "" + "body": "" + }, + "other-comment": { + "body": "", + "width": 14, + "height": 12 }, "other-default-tool-icon": { - "body": "" + "body": "" }, "other-icon-3-dots": { - "body": "", + "body": "", "width": 16, "height": 16 }, "other-message-3-fill": { - "body": "" + "body": "" }, "other-row-struct": { - "body": "", + "body": "", "width": 624, "height": 48 }, "other-slack": { - "body": "", + "body": "", "width": 27, "height": 27 }, "other-teams": { - "body": "", + "body": "", "width": 28, "height": 28 }, "plugins-google": { - "body": "", + "body": "", "width": 24, "height": 24 }, "plugins-partner-dark": { - "body": "" + "body": "" }, "plugins-partner-light": { - "body": "" + "body": "" }, "plugins-verified-dark": { - "body": "" + "body": "" }, "plugins-verified-light": { - "body": "" + "body": "" }, "plugins-web-reader": { - "body": "", + "body": "", "width": 24, "height": 24 }, "plugins-wikipedia": { - "body": "", + "body": "", "width": 24, "height": 24 }, "thought-data-set": { - "body": "" + "body": "" }, "thought-loading": { - "body": "" + "body": "" }, "thought-search": { - "body": "" + "body": "" }, "thought-thought-list": { - "body": "" + "body": "" }, "thought-web-reader": { - "body": "" + "body": "" }, "tracing-aliyun-icon": { - "body": "", + "body": "", "width": 65 }, "tracing-aliyun-icon-big": { - "body": "", + "body": "", "width": 96, "height": 24 }, "tracing-arize-icon": { - "body": "" + "body": "" }, "tracing-arize-icon-big": { - "body": "", + "body": "", "width": 111, "height": 24 }, "tracing-databricks-icon": { - "body": "", + "body": "", "width": 100 }, "tracing-databricks-icon-big": { - "body": "", + "body": "", "width": 151, "height": 24 }, "tracing-langfuse-icon": { - "body": "" + "body": "" }, "tracing-langfuse-icon-big": { - "body": "", + "body": "", "width": 111, "height": 24 }, "tracing-langsmith-icon": { - "body": "", + "body": "", "width": 84, "height": 14 }, "tracing-langsmith-icon-big": { - "body": "", + "body": "", "width": 124, "height": 20 }, "tracing-mlflow-icon": { - "body": "", + "body": "", "width": 43 }, "tracing-mlflow-icon-big": { - "body": "", + "body": "", "width": 65, "height": 24 }, "tracing-opik-icon": { - "body": "", + "body": "", "width": 47.134 }, "tracing-opik-icon-big": { - "body": "", + "body": "", "width": 70.701, "height": 24 }, "tracing-phoenix-icon": { - "body": "" + "body": "" }, "tracing-phoenix-icon-big": { - "body": "", + "body": "", "width": 111, "height": 24 }, "tracing-tencent-icon": { - "body": "", + "body": "", "width": 80, "height": 18 }, "tracing-tencent-icon-big": { - "body": "", + "body": "", "width": 80, "height": 18 }, "tracing-tracing-icon": { - "body": "", + "body": "", "width": 20, "height": 20 }, "tracing-weave-icon": { - "body": "", + "body": "", "width": 120 }, "tracing-weave-icon-big": { - "body": "", + "body": "", "width": 120 } } diff --git a/packages/iconify-collections/custom-public/info.json b/packages/iconify-collections/custom-public/info.json index 8b5572de6f..115e9e25f9 100644 --- a/packages/iconify-collections/custom-public/info.json +++ b/packages/iconify-collections/custom-public/info.json @@ -1,7 +1,7 @@ { "prefix": "custom-public", "name": "Dify Custom Public", - "total": 142, + "total": 144, "version": "0.0.0-private", "author": { "name": "LangGenius, Inc.", diff --git a/packages/iconify-collections/custom-vender/icons.json b/packages/iconify-collections/custom-vender/icons.json index a7dc8e75e0..bbed34e313 100644 --- a/packages/iconify-collections/custom-vender/icons.json +++ b/packages/iconify-collections/custom-vender/icons.json @@ -1,1096 +1,1096 @@ { "prefix": "custom-vender", - "lastModified": 1775115796, + "lastModified": 1776670621, "icons": { "features-citations": { - "body": "" + "body": "" }, "features-content-moderation": { - "body": "" + "body": "" }, "features-document": { - "body": "" + "body": "" }, "features-folder-upload": { - "body": "" + "body": "" }, "features-love-message": { - "body": "" + "body": "" }, "features-message-fast": { - "body": "" + "body": "" }, "features-microphone-01": { - "body": "" + "body": "" }, "features-text-to-audio": { - "body": "" + "body": "" }, "features-virtual-assistant": { - "body": "" + "body": "" }, "features-vision": { - "body": "" + "body": "" }, "knowledge-add-chunks": { - "body": "", + "body": "", "width": 20, "height": 20 }, "knowledge-api-aggregate": { - "body": "", + "body": "", "width": 16 }, "knowledge-arrow-shape": { - "body": "", + "body": "", "width": 24, "height": 11 }, "knowledge-chunk": { - "body": "", + "body": "", "width": 10, "height": 10 }, "knowledge-collapse": { - "body": "", + "body": "", "width": 16 }, "knowledge-divider": { - "body": "", + "body": "", "width": 6, "height": 30 }, "knowledge-economic": { - "body": "", + "body": "", "height": 18 }, "knowledge-full-text-search": { - "body": "", + "body": "", "width": 15 }, "knowledge-general-chunk": { - "body": "", + "body": "", "height": 18 }, "knowledge-high-quality": { - "body": "", + "body": "", "height": 18 }, "knowledge-hybrid-search": { - "body": "", + "body": "", "width": 16 }, "knowledge-parent-child-chunk": { - "body": "", + "body": "", "height": 18 }, "knowledge-question-and-answer": { - "body": "", + "body": "", "height": 18 }, "knowledge-search-lines-sparkle": { - "body": "", + "body": "", "width": 16 }, "knowledge-search-menu": { - "body": "", + "body": "", "width": 32, "height": 33 }, "knowledge-vector-search": { - "body": "", + "body": "", "width": 16 }, "line-alertsAndFeedback-alert-triangle": { - "body": "" + "body": "" }, "line-alertsAndFeedback-thumbs-down": { - "body": "" + "body": "" }, "line-alertsAndFeedback-thumbs-up": { - "body": "" + "body": "" }, "line-alertsAndFeedback-warning": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-arrows-arrow-narrow-left": { - "body": "", + "body": "", "width": 17, "height": 16 }, "line-arrows-arrow-up-right": { - "body": "" + "body": "" }, "line-arrows-chevron-down-double": { - "body": "", + "body": "", "width": 12, "height": 13 }, "line-arrows-chevron-right": { - "body": "" + "body": "" }, "line-arrows-chevron-selector-vertical": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-arrows-iconr": { - "body": "" + "body": "" }, "line-arrows-refresh-ccw-01": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-arrows-refresh-cw-05": { - "body": "", + "body": "", "width": 16, "height": 16 }, "line-arrows-reverse-left": { - "body": "", + "body": "", "width": 16, "height": 16 }, "line-communication-ai-text": { - "body": "" + "body": "" }, "line-communication-chat-bot": { - "body": "" + "body": "" }, "line-communication-chat-bot-slim": { - "body": "", + "body": "", "width": 48, "height": 48 }, "line-communication-cute-robot": { - "body": "" + "body": "" }, "line-communication-message-check-remove": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-communication-message-fast-plus": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-development-artificial-brain": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-development-bar-chart-square-02": { - "body": "" + "body": "" }, "line-development-brackets-x": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-development-code-browser": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-development-container": { - "body": "" + "body": "" }, "line-development-database-01": { - "body": "", + "body": "", "width": 17 }, "line-development-database-03": { - "body": "" + "body": "" }, "line-development-file-heart-02": { - "body": "" + "body": "" }, "line-development-git-branch-01": { - "body": "" + "body": "" }, "line-development-prompt-engineering": { - "body": "" + "body": "" }, "line-development-puzzle-piece-01": { - "body": "" + "body": "" }, "line-development-terminal-square": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-development-variable": { - "body": "" + "body": "" }, "line-development-webhooks": { - "body": "" + "body": "" }, "line-editor-align-left": { - "body": "" + "body": "" }, "line-editor-bezier-curve-03": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-editor-collapse": { - "body": "", + "body": "", "width": 16, "height": 16 }, "line-editor-colors": { - "body": "" + "body": "" }, "line-editor-image-indent-left": { - "body": "" + "body": "" }, "line-editor-left-indent-02": { - "body": "" + "body": "" }, "line-editor-letter-spacing-01": { - "body": "" + "body": "" }, "line-editor-type-square": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-education-book-open-01": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-files-copy": { - "body": "" + "body": "" }, "line-files-copy-check": { - "body": "" + "body": "" }, "line-files-file-02": { - "body": "" + "body": "" }, "line-files-file-arrow-01": { - "body": "" + "body": "" }, "line-files-file-check-02": { - "body": "" + "body": "" }, "line-files-file-download-02": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-files-file-plus-01": { - "body": "" + "body": "" }, "line-files-file-plus-02": { - "body": "" + "body": "" }, "line-files-file-text": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-files-file-upload": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-files-folder": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-financeAndECommerce-balance": { - "body": "" + "body": "" }, "line-financeAndECommerce-coins-stacked-01": { - "body": "" + "body": "" }, "line-financeAndECommerce-credits-coin": { - "body": "", + "body": "", "width": 10, "height": 10 }, "line-financeAndECommerce-gold-coin": { - "body": "", + "body": "", "width": 16, "height": 16 }, "line-financeAndECommerce-receipt-list": { - "body": "" + "body": "" }, "line-financeAndECommerce-tag-01": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-financeAndECommerce-tag-03": { - "body": "", + "body": "", "width": 16, "height": 16 }, "line-general-at-sign": { - "body": "" + "body": "" }, "line-general-bookmark": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-check": { - "body": "" + "body": "" }, "line-general-check-done-01": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-checklist-square": { - "body": "", + "body": "", "width": 32, "height": 32 }, "line-general-code-assistant": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-dots-grid": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-general-edit-02": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-general-edit-04": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-edit-05": { - "body": "" + "body": "" }, "line-general-hash-02": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-general-info-circle": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-general-link-03": { - "body": "", + "body": "", "width": 17 }, "line-general-link-external-02": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-general-log-in-04": { - "body": "" + "body": "" }, "line-general-log-out-01": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-general-log-out-04": { - "body": "" + "body": "" }, "line-general-magic-edit": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-menu-01": { - "body": "" + "body": "" }, "line-general-pin-01": { - "body": "" + "body": "" }, "line-general-pin-02": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-plus-02": { - "body": "", + "body": "", "width": 10, "height": 10 }, "line-general-refresh": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-search-menu": { - "body": "", + "body": "", "width": 32, "height": 32 }, "line-general-settings-01": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-general-settings-04": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-general-target-04": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-general-upload-03": { - "body": "" + "body": "" }, "line-general-upload-cloud-01": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-general-x": { - "body": "" + "body": "" }, "line-images-image-plus": { - "body": "" + "body": "" }, "line-layout-align-left-01": { - "body": "" + "body": "" }, "line-layout-align-right-01": { - "body": "" + "body": "" }, "line-layout-grid-01": { - "body": "", + "body": "", "width": 17, "height": 16 }, "line-layout-layout-grid-02": { - "body": "" + "body": "" }, "line-mediaAndDevices-microphone-01": { - "body": "" + "body": "" }, "line-mediaAndDevices-play-circle": { - "body": "" + "body": "" }, "line-mediaAndDevices-sliders-h": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-mediaAndDevices-speaker": { - "body": "" + "body": "" }, "line-mediaAndDevices-stop": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-mediaAndDevices-stop-circle": { - "body": "", + "body": "", "width": 17 }, "line-others-bubble-x": { - "body": "" + "body": "" }, "line-others-colors": { - "body": "", + "body": "", "width": 14, "height": 14 }, "line-others-drag-handle": { - "body": "" + "body": "" }, "line-others-env": { - "body": "" + "body": "" }, "line-others-global-variable": { - "body": "" + "body": "" }, "line-others-icon-3-dots": { - "body": "" + "body": "" }, "line-others-long-arrow-left": { - "body": "", + "body": "", "width": 21, "height": 8 }, "line-others-long-arrow-right": { - "body": "", + "body": "", "width": 26, "height": 8 }, "line-others-search-menu": { - "body": "", + "body": "", "width": 32, "height": 32 }, "line-others-tools": { - "body": "", + "body": "", "height": 17 }, "line-shapes-cube-outline": { - "body": "", + "body": "", "height": 17 }, "line-time-clock-fast-forward": { - "body": "", + "body": "", "width": 24, "height": 24 }, "line-time-clock-play": { - "body": "" + "body": "" }, "line-time-clock-play-slim": { - "body": "", + "body": "", "width": 32, "height": 32 }, "line-time-clock-refresh": { - "body": "", + "body": "", "width": 12, "height": 12 }, "line-users-user-01": { - "body": "" + "body": "" }, "line-users-users-01": { - "body": "" + "body": "" }, "line-weather-stars-02": { - "body": "", + "body": "", "width": 24, "height": 24 }, "other-anthropic-text": { - "body": "", + "body": "", "width": 90, "height": 20 }, "other-generator": { - "body": "" + "body": "" }, "other-group": { - "body": "", + "body": "", "height": 16 }, "other-hourglass-shape": { - "body": "", + "body": "", "width": 8 }, "other-mcp": { - "body": "", + "body": "", "width": 16, "height": 16 }, "other-no-tool-placeholder": { - "body": "", + "body": "", "width": 204, "height": 36 }, "other-openai": { - "body": "", + "body": "", "width": 80, "height": 22 }, "other-replay-line": { - "body": "", + "body": "", "width": 20, "height": 20 }, "other-square-checklist": { - "body": "", + "body": "", "width": 24, "height": 24 }, "pipeline-input-field": { - "body": "", + "body": "", "width": 16, "height": 16 }, "pipeline-pipeline-fill": { - "body": "" + "body": "" }, "pipeline-pipeline-line": { - "body": "" + "body": "" }, "plugin-box-sparkle-fill": { - "body": "", + "body": "", "width": 14, "height": 14 }, "plugin-left-corner": { - "body": "", + "body": "", "width": 13, "height": 20 }, "plugin-trigger": { - "body": "" + "body": "" }, "solid-FinanceAndECommerce-gold-coin": { - "body": "" + "body": "" }, "solid-FinanceAndECommerce-scales-02": { - "body": "" + "body": "" }, "solid-alertsAndFeedback-alert-triangle": { - "body": "", + "body": "", "width": 12, "height": 12 }, "solid-arrows-arrow-down-double-line": { - "body": "" + "body": "" }, "solid-arrows-arrow-down-round-fill": { - "body": "" + "body": "" }, "solid-arrows-arrow-up-double-line": { - "body": "" + "body": "" }, "solid-arrows-chevron-down": { - "body": "", + "body": "", "width": 24, "height": 24 }, "solid-arrows-high-priority": { - "body": "", + "body": "", "width": 24, "height": 24 }, "solid-communication-ai-text": { - "body": "" + "body": "" }, "solid-communication-bubble-text-mod": { - "body": "" + "body": "" }, "solid-communication-chat-bot": { - "body": "", + "body": "", "width": 13, "height": 12 }, "solid-communication-cute-robot": { - "body": "" + "body": "" }, "solid-communication-edit-list": { - "body": "" + "body": "" }, "solid-communication-list-sparkle": { - "body": "" + "body": "" }, "solid-communication-logic": { - "body": "" + "body": "" }, "solid-communication-message-dots-circle": { - "body": "" + "body": "" }, "solid-communication-message-fast": { - "body": "" + "body": "" }, "solid-communication-message-heart-circle": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-communication-message-smile-square": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-communication-send-03": { - "body": "", + "body": "", "width": 20, "height": 20 }, "solid-development-api-connection": { - "body": "", + "body": "", "width": 24, "height": 24 }, "solid-development-api-connection-mod": { - "body": "" + "body": "" }, "solid-development-bar-chart-square-02": { - "body": "" + "body": "" }, "solid-development-container": { - "body": "", + "body": "", "width": 17 }, "solid-development-database-02": { - "body": "", + "body": "", "width": 17 }, "solid-development-database-03": { - "body": "" + "body": "" }, "solid-development-file-heart-02": { - "body": "" + "body": "" }, "solid-development-pattern-recognition": { - "body": "", + "body": "", "width": 24, "height": 24 }, "solid-development-prompt-engineering": { - "body": "" + "body": "" }, "solid-development-puzzle-piece-01": { - "body": "", + "body": "", "width": 17 }, "solid-development-semantic": { - "body": "", + "body": "", "width": 24, "height": 24 }, "solid-development-terminal-square": { - "body": "", + "body": "", "width": 12, "height": 12 }, "solid-development-variable-02": { - "body": "", + "body": "", "width": 24, "height": 24 }, "solid-editor-brush-01": { - "body": "" + "body": "" }, "solid-editor-citations": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-editor-colors": { - "body": "" + "body": "" }, "solid-editor-paragraph": { - "body": "" + "body": "" }, "solid-editor-type-square": { - "body": "" + "body": "" }, "solid-education-beaker-02": { - "body": "", + "body": "", "width": 12, "height": 12 }, "solid-education-bubble-text": { - "body": "" + "body": "" }, "solid-education-heart-02": { - "body": "" + "body": "" }, "solid-education-unblur": { - "body": "" + "body": "" }, "solid-files-file-05": { - "body": "" + "body": "" }, "solid-files-file-search-02": { - "body": "" + "body": "" }, "solid-files-file-zip": { - "body": "" + "body": "" }, "solid-files-folder": { - "body": "" + "body": "" }, "solid-general-answer-triangle": { - "body": "", + "body": "", "width": 8, "height": 12 }, "solid-general-arrow-down-round-fill": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-general-check-circle": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-general-check-done-01": { - "body": "" + "body": "" }, "solid-general-download-02": { - "body": "" + "body": "" }, "solid-general-edit-03": { - "body": "", + "body": "", "width": 12, "height": 12 }, "solid-general-edit-04": { - "body": "" + "body": "" }, "solid-general-eye": { - "body": "" + "body": "" }, "solid-general-github": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-general-message-clock-circle": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-general-plus-circle": { - "body": "" + "body": "" }, "solid-general-question-triangle": { - "body": "", + "body": "", "width": 8, "height": 12 }, "solid-general-search-md": { - "body": "" + "body": "" }, "solid-general-target-04": { - "body": "" + "body": "" }, "solid-general-tool-03": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-general-x-circle": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-general-zap-fast": { - "body": "", + "body": "", "width": 12, "height": 12 }, "solid-general-zap-narrow": { - "body": "", + "body": "", "width": 12, "height": 12 }, "solid-layout-grid-01": { - "body": "" + "body": "" }, "solid-mediaAndDevices-audio-support-icon": { - "body": "" + "body": "" }, "solid-mediaAndDevices-document-support-icon": { - "body": "" + "body": "" }, "solid-mediaAndDevices-magic-box": { - "body": "" + "body": "" }, "solid-mediaAndDevices-magic-eyes": { - "body": "" + "body": "" }, "solid-mediaAndDevices-magic-wand": { - "body": "" + "body": "" }, "solid-mediaAndDevices-microphone-01": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-mediaAndDevices-play": { - "body": "" + "body": "" }, "solid-mediaAndDevices-robot": { - "body": "" + "body": "" }, "solid-mediaAndDevices-sliders-02": { - "body": "", + "body": "", "width": 24, "height": 24 }, "solid-mediaAndDevices-speaker": { - "body": "", + "body": "", "width": 16, "height": 16 }, "solid-mediaAndDevices-stop-circle": { - "body": "", + "body": "", "width": 20, "height": 20 }, "solid-mediaAndDevices-video-support-icon": { - "body": "" + "body": "" }, "solid-security-lock-01": { - "body": "", + "body": "", "width": 12, "height": 12 }, "solid-shapes-corner": { - "body": "", + "body": "", "width": 13, "height": 20 }, "solid-shapes-star-04": { - "body": "", + "body": "", "width": 11, "height": 10 }, "solid-shapes-star-06": { - "body": "" + "body": "" }, "solid-users-user-01": { - "body": "" + "body": "" }, "solid-users-user-edit-02": { - "body": "", + "body": "", "width": 14, "height": 14 }, "solid-users-users-01": { - "body": "" + "body": "" }, "solid-users-users-plus": { - "body": "", + "body": "", "width": 24, "height": 24 }, "system-auto-update-line": { - "body": "", + "body": "", "width": 24, "height": 24 }, "workflow-agent": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-answer": { - "body": "" + "body": "" }, "workflow-api-aggregate": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-assigner": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-asterisk": { - "body": "" + "body": "" }, "workflow-calendar-check-line": { - "body": "" + "body": "" }, "workflow-code": { - "body": "" + "body": "" }, "workflow-datasource": { - "body": "" + "body": "" }, "workflow-docs-extractor": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-end": { - "body": "" + "body": "" }, "workflow-home": { - "body": "" + "body": "" }, "workflow-http": { - "body": "" + "body": "" }, "workflow-human-in-loop": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-if-else": { - "body": "" + "body": "" }, "workflow-iteration": { - "body": "" + "body": "" }, "workflow-iteration-start": { - "body": "", + "body": "", "width": 12, "height": 12 }, "workflow-jinja": { - "body": "", + "body": "", "width": 24, "height": 12 }, "workflow-knowledge-base": { - "body": "" + "body": "" }, "workflow-knowledge-retrieval": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-list-filter": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-llm": { - "body": "" + "body": "" }, "workflow-loop": { - "body": "", + "body": "", "width": 18, "height": 16 }, "workflow-loop-end": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-parameter-extractor": { - "body": "" + "body": "" }, "workflow-question-classifier": { - "body": "" + "body": "" }, "workflow-schedule": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-templating-transform": { - "body": "" + "body": "" }, "workflow-trigger-all": { - "body": "" + "body": "" }, "workflow-variable-x": { - "body": "" + "body": "" }, "workflow-webhook-line": { - "body": "", + "body": "", "width": 16, "height": 16 }, "workflow-window-cursor": { - "body": "", + "body": "", "width": 16, "height": 16 } diff --git a/packages/iconify-collections/package.json b/packages/iconify-collections/package.json index 07c29f0a07..752b7ce437 100644 --- a/packages/iconify-collections/package.json +++ b/packages/iconify-collections/package.json @@ -23,9 +23,10 @@ "./custom-vender/chars.json": "./custom-vender/chars.json" }, "scripts": { - "generate": "node ./scripts/generate-collections.mjs" + "generate": "tsx ./scripts/generate-collections.ts" }, "devDependencies": { - "iconify-import-svg": "catalog:" + "iconify-import-svg": "catalog:", + "tsx": "catalog:" } } diff --git a/packages/iconify-collections/scripts/generate-collections.mjs b/packages/iconify-collections/scripts/generate-collections.ts similarity index 59% rename from packages/iconify-collections/scripts/generate-collections.mjs rename to packages/iconify-collections/scripts/generate-collections.ts index 1c734731e6..5cc67dd588 100644 --- a/packages/iconify-collections/scripts/generate-collections.mjs +++ b/packages/iconify-collections/scripts/generate-collections.ts @@ -3,45 +3,62 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import { importSvgCollections } from 'iconify-import-svg' +type IconData = { + body: string + left?: number + top?: number + width?: number + height?: number + rotate?: 0 | 1 | 2 | 3 + hFlip?: boolean + vFlip?: boolean +} + +type AliasData = Omit & { + parent: string +} + +type ImportedCollection = { + icons?: Record + aliases?: Record + lastModified?: number +} + +type ImportedCollections = Record + +type CollectionInfo = { + prefix: string + name: string + total: number + version: string + author: { + name: string + url: string + } + license: { + title: string + spdx: string + url: string + } + samples: string[] + palette: false +} + +type PackageJson = { + version: string +} + const __dirname = path.dirname(fileURLToPath(import.meta.url)) const packageDir = path.resolve(__dirname, '..') -const parseColorOptions = { - fallback: () => 'currentColor', -} -const svgOptimizeConfig = { - cleanupSVG: true, - deOptimisePaths: true, - runSVGO: true, - parseColors: parseColorOptions, -} - -const customPublicCollections = importSvgCollections({ - source: path.resolve(packageDir, 'assets/public'), - prefix: 'custom-public', - ignoreImportErrors: true, - ...svgOptimizeConfig, -}) - -const customVenderCollections = importSvgCollections({ - source: path.resolve(packageDir, 'assets/vender'), - prefix: 'custom-vender', - ignoreImportErrors: true, - ...svgOptimizeConfig, -}) - -const packageJson = JSON.parse(await readFile(path.resolve(packageDir, 'package.json'), 'utf8')) - -const flattenCollections = (collections, prefix) => { - const icons = {} - const aliases = {} +const flattenCollections = (collections: ImportedCollections, prefix: string) => { + const icons: Record = {} + const aliases: Record = {} let lastModified = 0 for (const [collectionKey, collection] of Object.entries(collections)) { const segment = collectionKey.slice(prefix.length + 1) - const namePrefix = segment - ? `${segment}-` - : '' + const namePrefix = segment ? `${segment}-` : '' for (const [iconName, iconData] of Object.entries(collection.icons ?? {})) icons[`${namePrefix}${iconName}`] = iconData @@ -61,11 +78,38 @@ const flattenCollections = (collections, prefix) => { } } -const createCollectionInfo = (prefix, name, icons) => ({ +const customPublicCollections = importSvgCollections({ + source: path.resolve(packageDir, 'assets/public'), + prefix: 'custom-public', + ignoreImportErrors: true, + cleanupSVG: true, + deOptimisePaths: true, + runSVGO: true, + parseColors: false, +}) as ImportedCollections + +const customVenderCollections = importSvgCollections({ + source: path.resolve(packageDir, 'assets/vender'), + prefix: 'custom-vender', + ignoreImportErrors: true, + cleanupSVG: true, + deOptimisePaths: true, + runSVGO: false, + parseColors: { + callback: () => 'currentColor', + }, +}) as ImportedCollections + +const createCollectionInfo = ( + prefix: string, + name: string, + icons: Record, + version: string, +): CollectionInfo => ({ prefix, name, total: Object.keys(icons).length, - version: packageJson.version, + version, author: { name: 'LangGenius, Inc.', url: 'https://github.com/langgenius/dify', @@ -79,7 +123,7 @@ const createCollectionInfo = (prefix, name, icons) => ({ palette: false, }) -const createIndexMjs = () => `import icons from './icons.json' with { type: 'json' } +const createIndexMjs = (): string => `import icons from './icons.json' with { type: 'json' } import info from './info.json' with { type: 'json' } import metadata from './metadata.json' with { type: 'json' } import chars from './chars.json' with { type: 'json' } @@ -87,7 +131,7 @@ import chars from './chars.json' with { type: 'json' } export { icons, info, metadata, chars } ` -const createIndexJs = () => `'use strict' +const createIndexJs = (): string => `'use strict' const icons = require('./icons.json') const info = require('./info.json') @@ -97,7 +141,7 @@ const chars = require('./chars.json') module.exports = { icons, info, metadata, chars } ` -const createIndexTypes = () => `export interface IconifyJSON { +const createIndexTypes = (): string => `export interface IconifyJSON { prefix: string icons: Record aliases?: Record @@ -153,9 +197,14 @@ export declare const metadata: IconifyMetaData export declare const chars: IconifyChars ` -const writeCollectionPackage = async (directoryName, collection, name) => { +const writeCollectionPackage = async ( + directoryName: string, + collection: ReturnType, + name: string, + version: string, +): Promise => { const targetDir = path.resolve(packageDir, directoryName) - const info = createCollectionInfo(collection.prefix, name, collection.icons) + const info = createCollectionInfo(collection.prefix, name, collection.icons, version) await mkdir(targetDir, { recursive: true }) await writeFile(path.resolve(targetDir, 'icons.json'), `${JSON.stringify(collection, null, 2)}\n`) @@ -167,12 +216,32 @@ const writeCollectionPackage = async (directoryName, collection, name) => { await writeFile(path.resolve(targetDir, 'index.d.ts'), `${createIndexTypes()}\n`) } -const mergedCustomPublicCollection = flattenCollections(customPublicCollections, 'custom-public') -const mergedCustomVenderCollection = flattenCollections(customVenderCollections, 'custom-vender') +async function main(): Promise { + const packageJson = JSON.parse( + await readFile(path.resolve(packageDir, 'package.json'), 'utf8'), + ) as PackageJson + const customPublicCollection = flattenCollections(customPublicCollections, 'custom-public') + const customVenderCollection = flattenCollections(customVenderCollections, 'custom-vender') -await rm(path.resolve(packageDir, 'src'), { recursive: true, force: true }) -await rm(path.resolve(packageDir, 'custom-public'), { recursive: true, force: true }) -await rm(path.resolve(packageDir, 'custom-vender'), { recursive: true, force: true }) + await rm(path.resolve(packageDir, 'src'), { recursive: true, force: true }) + await rm(path.resolve(packageDir, 'custom-public'), { recursive: true, force: true }) + await rm(path.resolve(packageDir, 'custom-vender'), { recursive: true, force: true }) -await writeCollectionPackage('custom-public', mergedCustomPublicCollection, 'Dify Custom Public') -await writeCollectionPackage('custom-vender', mergedCustomVenderCollection, 'Dify Custom Vender') + await writeCollectionPackage( + 'custom-public', + customPublicCollection, + 'Dify Custom Public', + packageJson.version, + ) + await writeCollectionPackage( + 'custom-vender', + customVenderCollection, + 'Dify Custom Vender', + packageJson.version, + ) +} + +main().catch((error: unknown) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65f2e05bf4..0c276fae9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -352,8 +352,8 @@ catalogs: specifier: 1.2.1 version: 1.2.1 iconify-import-svg: - specifier: 0.1.2 - version: 0.1.2 + specifier: 0.2.0 + version: 0.2.0 immer: specifier: 11.1.4 version: 11.1.4 @@ -718,7 +718,10 @@ importers: devDependencies: iconify-import-svg: specifier: 'catalog:' - version: 0.1.2 + version: 0.2.0 + tsx: + specifier: 'catalog:' + version: 4.21.0 packages/migrate-no-unchecked-indexed-access: dependencies: @@ -5932,8 +5935,8 @@ packages: typescript: optional: true - iconify-import-svg@0.1.2: - resolution: {integrity: sha512-8dwxdGK1a7oPDQhLQOPTbx51tpkxYB6HZvf4fxWz2QVYqEtgop0FWE7OXQ+4zqnrTVUpMIGnOsvqIHtPBK9Isw==} + iconify-import-svg@0.2.0: + resolution: {integrity: sha512-NFuDyiYRKLSNvbiUnR4627DF4QjQR+bC+n+Nh0lcMnKXv9MCwzikOcdzqITU1yFfRacc6S6PeElc2H5l+35T1Q==} iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} @@ -13329,7 +13332,7 @@ snapshots: optionalDependencies: typescript: 6.0.2 - iconify-import-svg@0.1.2: + iconify-import-svg@0.2.0: dependencies: '@iconify/tools': 4.2.0 '@iconify/types': 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3dd5b403a3..f02d05b233 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -163,7 +163,7 @@ catalog: html-to-image: 1.11.13 i18next: 26.0.4 i18next-resources-to-backend: 1.2.1 - iconify-import-svg: 0.1.2 + iconify-import-svg: 0.2.0 immer: 11.1.4 jotai: 2.19.1 js-audio-recorder: 1.0.7 From 3cd6ef44644c888ca693067ea14cad7d5defd4c4 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 20 Apr 2026 16:47:15 +0800 Subject: [PATCH 006/253] chore: export dsl add loading (#35427) --- .../__tests__/app-info-modals.spec.tsx | 40 ++++++++++++++- .../app-sidebar/app-info/app-info-modals.tsx | 29 +++++++++-- .../app-info/use-app-info-actions.ts | 8 +-- .../dsl-export-confirm-modal.spec.tsx | 38 +++++++++++++- .../workflow/dsl-export-confirm-modal.tsx | 51 +++++++++++++++---- web/i18n/ar-TN/common.json | 1 + web/i18n/de-DE/common.json | 1 + web/i18n/en-US/common.json | 1 + web/i18n/es-ES/common.json | 1 + web/i18n/fa-IR/common.json | 1 + web/i18n/fr-FR/common.json | 1 + web/i18n/hi-IN/common.json | 1 + web/i18n/id-ID/common.json | 1 + web/i18n/it-IT/common.json | 1 + web/i18n/ja-JP/common.json | 1 + web/i18n/ko-KR/common.json | 1 + web/i18n/nl-NL/common.json | 1 + web/i18n/pl-PL/common.json | 1 + web/i18n/pt-BR/common.json | 1 + web/i18n/ro-RO/common.json | 1 + web/i18n/ru-RU/common.json | 1 + web/i18n/sl-SI/common.json | 1 + web/i18n/th-TH/common.json | 1 + web/i18n/tr-TR/common.json | 1 + web/i18n/uk-UA/common.json | 1 + web/i18n/vi-VN/common.json | 1 + web/i18n/zh-Hans/common.json | 1 + web/i18n/zh-Hant/common.json | 1 + 28 files changed, 169 insertions(+), 20 deletions(-) diff --git a/web/app/components/app-sidebar/app-info/__tests__/app-info-modals.spec.tsx b/web/app/components/app-sidebar/app-info/__tests__/app-info-modals.spec.tsx index 707b0da267..2fdd35cc43 100644 --- a/web/app/components/app-sidebar/app-info/__tests__/app-info-modals.spec.tsx +++ b/web/app/components/app-sidebar/app-info/__tests__/app-info-modals.spec.tsx @@ -75,9 +75,9 @@ const defaultProps = { setSecretEnvList: vi.fn(), onEdit: vi.fn(), onCopy: vi.fn(), - onExport: vi.fn(), + onExport: vi.fn(async () => {}), exportCheck: vi.fn(), - handleConfirmExport: vi.fn(), + handleConfirmExport: vi.fn(async () => {}), onConfirmDelete: vi.fn(), } @@ -238,6 +238,42 @@ describe('AppInfoModals', () => { expect(defaultProps.handleConfirmExport).toHaveBeenCalledTimes(1) }) + it('should disable export confirm button and avoid duplicate submits while confirming export', async () => { + let resolveConfirmExport: () => void + const handleConfirmExport = vi.fn(() => new Promise((resolve) => { + resolveConfirmExport = resolve + })) + const user = userEvent.setup() + + await act(async () => { + render( + , + ) + }) + + const confirmButton = await screen.findByRole('button', { name: 'common.operation.confirm' }) + + const firstClick = user.click(confirmButton) + await waitFor(() => { + expect(confirmButton).toBeDisabled() + expect(confirmButton).toHaveTextContent('common.operation.exporting') + }) + await user.click(confirmButton) + + expect(handleConfirmExport).toHaveBeenCalledTimes(1) + + resolveConfirmExport!() + await firstClick + await waitFor(() => { + expect(confirmButton).not.toBeDisabled() + expect(confirmButton).toHaveTextContent('common.operation.confirm') + }) + }) + it('should call exportCheck when backup on importDSL modal', async () => { const user = userEvent.setup() await act(async () => { diff --git a/web/app/components/app-sidebar/app-info/app-info-modals.tsx b/web/app/components/app-sidebar/app-info/app-info-modals.tsx index 7d142e0cc6..9535725cd3 100644 --- a/web/app/components/app-sidebar/app-info/app-info-modals.tsx +++ b/web/app/components/app-sidebar/app-info/app-info-modals.tsx @@ -13,7 +13,7 @@ import { AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' import * as React from 'react' -import { useState } from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import dynamic from '@/next/dynamic' @@ -34,7 +34,7 @@ type AppInfoModalsProps = { onCopy: DuplicateAppModalProps['onConfirm'] onExport: (include?: boolean) => Promise exportCheck: () => void - handleConfirmExport: () => void + handleConfirmExport: () => Promise onConfirmDelete: () => void } @@ -53,6 +53,7 @@ const AppInfoModals = ({ }: AppInfoModalsProps) => { const { t } = useTranslation() const [confirmDeleteInput, setConfirmDeleteInput] = useState('') + const [isConfirmingExport, setIsConfirmingExport] = useState(false) const isDeleteConfirmDisabled = confirmDeleteInput !== appDetail.name const handleDeleteDialogClose = () => { @@ -60,6 +61,19 @@ const AppInfoModals = ({ closeModal() } + const handleExportWarningConfirm = useCallback(async () => { + if (isConfirmingExport) + return + + setIsConfirmingExport(true) + try { + await handleConfirmExport() + } + finally { + setIsConfirmingExport(false) + } + }, [handleConfirmExport, isConfirmingExport]) + return ( <> {activeModal === 'switch' && ( @@ -161,8 +175,15 @@ const AppInfoModals = ({
{t('operation.cancel', { ns: 'common' })} - - {t('operation.confirm', { ns: 'common' })} + + {isConfirmingExport + ? t('operation.exporting', { ns: 'common' }) + : t('operation.confirm', { ns: 'common' })} diff --git a/web/app/components/app-sidebar/app-info/use-app-info-actions.ts b/web/app/components/app-sidebar/app-info/use-app-info-actions.ts index 5ca0e85c18..262a8c7db0 100644 --- a/web/app/components/app-sidebar/app-info/use-app-info-actions.ts +++ b/web/app/components/app-sidebar/app-info/use-app-info-actions.ts @@ -62,7 +62,7 @@ export function useAppInfoActions({ onDetailExpand }: UseAppInfoActionsParams) { timestamp: Date.now(), }) }) - .catch(() => {}) + .catch(() => { }) }, [appDetail?.id]) useEffect(() => { @@ -89,7 +89,7 @@ export function useAppInfoActions({ onDetailExpand }: UseAppInfoActionsParams) { } }) }) - .catch(() => {}) + .catch(() => { }) return () => { disposed = true @@ -183,7 +183,6 @@ export function useAppInfoActions({ onDetailExpand }: UseAppInfoActionsParams) { const handleConfirmExport = useCallback(async () => { if (!appDetail) return - closeModal() try { const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`) const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret') @@ -196,6 +195,9 @@ export function useAppInfoActions({ onDetailExpand }: UseAppInfoActionsParams) { catch { toast(t('exportFailed', { ns: 'app' }), { type: 'error' }) } + finally { + closeModal() + } }, [appDetail, closeModal, onExport, t]) const onConfirmDelete = useCallback(async () => { diff --git a/web/app/components/workflow/__tests__/dsl-export-confirm-modal.spec.tsx b/web/app/components/workflow/__tests__/dsl-export-confirm-modal.spec.tsx index 1e0ba380cd..d7753d163e 100644 --- a/web/app/components/workflow/__tests__/dsl-export-confirm-modal.spec.tsx +++ b/web/app/components/workflow/__tests__/dsl-export-confirm-modal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import DSLExportConfirmModal from '../dsl-export-confirm-modal' @@ -105,6 +105,42 @@ describe('DSLExportConfirmModal', () => { expect(onClose).toHaveBeenCalledTimes(1) }) + it('should show exporting state and prevent duplicate submits while exporting', async () => { + let resolveConfirm: () => void + const onConfirm = vi.fn(() => new Promise((resolve) => { + resolveConfirm = resolve + })) + const onClose = vi.fn() + const user = userEvent.setup() + + render( + , + ) + + const confirmButton = screen.getByRole('button', { name: 'workflow.env.export.ignore' }) + + const firstClick = user.click(confirmButton) + await waitFor(() => { + expect(confirmButton).toBeDisabled() + expect(confirmButton).toHaveTextContent('common.operation.exporting') + expect(screen.getByRole('button', { name: 'common.operation.cancel' })).toBeDisabled() + }) + + await user.click(confirmButton) + + expect(onConfirm).toHaveBeenCalledTimes(1) + expect(onClose).not.toHaveBeenCalled() + + resolveConfirm!() + await firstClick + + await waitFor(() => expect(onClose).toHaveBeenCalledTimes(1)) + }) + it('should render border separators for all rows except the last one', () => { render( void + onConfirm: (state: boolean) => void | Promise onClose: () => void } @@ -25,11 +25,21 @@ const DSLExportConfirmModal = ({ const { t } = useTranslation() const [exportSecrets, setExportSecrets] = useState(false) + const [isExporting, setIsExporting] = useState(false) - const submit = () => { - onConfirm(exportSecrets) - onClose() - } + const submit = useCallback(async () => { + if (isExporting) + return + + setIsExporting(true) + try { + await onConfirm(exportSecrets) + onClose() + } + finally { + setIsExporting(false) + } + }, [exportSecrets, isExporting, onClose, onConfirm]) return (
{t('env.export.title', { ns: 'workflow' })}
-
+
!isExporting && onClose()} + >
@@ -72,13 +85,31 @@ const DSLExportConfirmModal = ({ setExportSecrets(!exportSecrets)} /> -
setExportSecrets(!exportSecrets)}>{t('env.export.checkbox', { ns: 'workflow' })}
+
!isExporting && setExportSecrets(!exportSecrets)} + > + {t('env.export.checkbox', { ns: 'workflow' })} +
- - + +
) diff --git a/web/i18n/ar-TN/common.json b/web/i18n/ar-TN/common.json index 22ede7acc2..893b9bdbfe 100644 --- a/web/i18n/ar-TN/common.json +++ b/web/i18n/ar-TN/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "اكتمل التنزيل.", "operation.duplicate": "تكرار", "operation.edit": "تعديل", + "operation.exporting": "جارٍ التصدير", "operation.format": "تنسيق", "operation.getForFree": "احصل عليه مجانا", "operation.imageCopied": "تم نسخ الصورة", diff --git a/web/i18n/de-DE/common.json b/web/i18n/de-DE/common.json index 582bf758a8..f24f94fb22 100644 --- a/web/i18n/de-DE/common.json +++ b/web/i18n/de-DE/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Download abgeschlossen.", "operation.duplicate": "Duplikat", "operation.edit": "Bearbeiten", + "operation.exporting": "Exportiere", "operation.format": "Format", "operation.getForFree": "Kostenlos erhalten", "operation.imageCopied": "Kopiertes Bild", diff --git a/web/i18n/en-US/common.json b/web/i18n/en-US/common.json index d8a312d573..d4f72c242d 100644 --- a/web/i18n/en-US/common.json +++ b/web/i18n/en-US/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Download Completed.", "operation.duplicate": "Duplicate", "operation.edit": "Edit", + "operation.exporting": "Exporting", "operation.format": "Format", "operation.getForFree": "Get for free", "operation.imageCopied": "Image copied", diff --git a/web/i18n/es-ES/common.json b/web/i18n/es-ES/common.json index a7279bfc8f..649ac803aa 100644 --- a/web/i18n/es-ES/common.json +++ b/web/i18n/es-ES/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Descarga completada.", "operation.duplicate": "Duplicar", "operation.edit": "Editar", + "operation.exporting": "Exportando", "operation.format": "Formato", "operation.getForFree": "Obtener gratis", "operation.imageCopied": "Imagen copiada", diff --git a/web/i18n/fa-IR/common.json b/web/i18n/fa-IR/common.json index 7e90357db2..ba1179520e 100644 --- a/web/i18n/fa-IR/common.json +++ b/web/i18n/fa-IR/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "دانلود کامل شد.", "operation.duplicate": "تکرار", "operation.edit": "ویرایش", + "operation.exporting": "در حال خروجی گرفتن", "operation.format": "قالب", "operation.getForFree": "دریافت رایگان", "operation.imageCopied": "تصویر کپی شده", diff --git a/web/i18n/fr-FR/common.json b/web/i18n/fr-FR/common.json index fd92838b55..ea53de39d0 100644 --- a/web/i18n/fr-FR/common.json +++ b/web/i18n/fr-FR/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Téléchargement terminé.", "operation.duplicate": "Dupliquer", "operation.edit": "Modifier", + "operation.exporting": "Exportation", "operation.format": "Format", "operation.getForFree": "Obtenez gratuitement", "operation.imageCopied": "Image copied", diff --git a/web/i18n/hi-IN/common.json b/web/i18n/hi-IN/common.json index 4d2ac80d97..736618363f 100644 --- a/web/i18n/hi-IN/common.json +++ b/web/i18n/hi-IN/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "डाउनलोड पूरा हुआ।", "operation.duplicate": "डुप्लिकेट", "operation.edit": "संपादित करें", + "operation.exporting": "निर्यात हो रहा है", "operation.format": "फॉर्मेट", "operation.getForFree": "मुफ्त में प्राप्त करें", "operation.imageCopied": "कॉपी की गई छवि", diff --git a/web/i18n/id-ID/common.json b/web/i18n/id-ID/common.json index 74f5e3593b..8bb344acb8 100644 --- a/web/i18n/id-ID/common.json +++ b/web/i18n/id-ID/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Unduh Selesai.", "operation.duplicate": "Duplikat", "operation.edit": "Mengedit", + "operation.exporting": "Mengekspor", "operation.format": "Format", "operation.getForFree": "Dapatkan gratis", "operation.imageCopied": "Gambar yang disalin", diff --git a/web/i18n/it-IT/common.json b/web/i18n/it-IT/common.json index f994f5cc26..803586e584 100644 --- a/web/i18n/it-IT/common.json +++ b/web/i18n/it-IT/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Download completato.", "operation.duplicate": "Duplica", "operation.edit": "Modifica", + "operation.exporting": "Esportazione in corso", "operation.format": "Formato", "operation.getForFree": "Ottieni gratuitamente", "operation.imageCopied": "Immagine copiata", diff --git a/web/i18n/ja-JP/common.json b/web/i18n/ja-JP/common.json index 938c02f032..700e9aa11b 100644 --- a/web/i18n/ja-JP/common.json +++ b/web/i18n/ja-JP/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "ダウンロード完了", "operation.duplicate": "複製", "operation.edit": "編集", + "operation.exporting": "エクスポート中", "operation.format": "フォーマット", "operation.getForFree": "無料で入手", "operation.imageCopied": "コピーした画像", diff --git a/web/i18n/ko-KR/common.json b/web/i18n/ko-KR/common.json index e7ba49a8a3..280b7fcfce 100644 --- a/web/i18n/ko-KR/common.json +++ b/web/i18n/ko-KR/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "다운로드 완료.", "operation.duplicate": "중복", "operation.edit": "편집", + "operation.exporting": "내보내는 중", "operation.format": "형식", "operation.getForFree": "무료로 받기", "operation.imageCopied": "복사된 이미지", diff --git a/web/i18n/nl-NL/common.json b/web/i18n/nl-NL/common.json index 3fdf51d8b0..6336dd7001 100644 --- a/web/i18n/nl-NL/common.json +++ b/web/i18n/nl-NL/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Download Completed.", "operation.duplicate": "Duplicate", "operation.edit": "Edit", + "operation.exporting": "Exporteren", "operation.format": "Format", "operation.getForFree": "Get for free", "operation.imageCopied": "Image copied", diff --git a/web/i18n/pl-PL/common.json b/web/i18n/pl-PL/common.json index d623672981..ceeab09acd 100644 --- a/web/i18n/pl-PL/common.json +++ b/web/i18n/pl-PL/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Pobieranie zakończone.", "operation.duplicate": "Duplikuj", "operation.edit": "Edytuj", + "operation.exporting": "Eksportowanie", "operation.format": "Format", "operation.getForFree": "Zdobądź za darmo", "operation.imageCopied": "Skopiowany obraz", diff --git a/web/i18n/pt-BR/common.json b/web/i18n/pt-BR/common.json index 83d2183a37..cd7d8a5487 100644 --- a/web/i18n/pt-BR/common.json +++ b/web/i18n/pt-BR/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Download concluído.", "operation.duplicate": "Duplicada", "operation.edit": "Editar", + "operation.exporting": "Exportando", "operation.format": "Formato", "operation.getForFree": "Obter gratuitamente", "operation.imageCopied": "Imagem copiada", diff --git a/web/i18n/ro-RO/common.json b/web/i18n/ro-RO/common.json index 508c026156..0d6dfa6c0f 100644 --- a/web/i18n/ro-RO/common.json +++ b/web/i18n/ro-RO/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Descărcarea a fost finalizată.", "operation.duplicate": "Duplică", "operation.edit": "Editează", + "operation.exporting": "Se exportă", "operation.format": "Format", "operation.getForFree": "Obține gratuit", "operation.imageCopied": "Imagine copiată", diff --git a/web/i18n/ru-RU/common.json b/web/i18n/ru-RU/common.json index cdfdc6d372..9e984bfa6e 100644 --- a/web/i18n/ru-RU/common.json +++ b/web/i18n/ru-RU/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Загрузка завершена.", "operation.duplicate": "Дублировать", "operation.edit": "Редактировать", + "operation.exporting": "Экспорт", "operation.format": "Формат", "operation.getForFree": "Получить бесплатно", "operation.imageCopied": "Скопированное изображение", diff --git a/web/i18n/sl-SI/common.json b/web/i18n/sl-SI/common.json index ffc370eb51..ff4e230a88 100644 --- a/web/i18n/sl-SI/common.json +++ b/web/i18n/sl-SI/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Prenos končan.", "operation.duplicate": "Podvoji", "operation.edit": "Uredi", + "operation.exporting": "Izvažanje", "operation.format": "Format", "operation.getForFree": "Dobite brezplačno", "operation.imageCopied": "Kopirana slika", diff --git a/web/i18n/th-TH/common.json b/web/i18n/th-TH/common.json index b193c960cf..b0e20f5e4f 100644 --- a/web/i18n/th-TH/common.json +++ b/web/i18n/th-TH/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "ดาวน์โหลดเสร็จสิ้นแล้ว.", "operation.duplicate": "สำเนา", "operation.edit": "แก้ไข", + "operation.exporting": "กำลังส่งออก", "operation.format": "รูปแบบ", "operation.getForFree": "รับฟรี", "operation.imageCopied": "ภาพที่คัดลอก", diff --git a/web/i18n/tr-TR/common.json b/web/i18n/tr-TR/common.json index 1ebaf13758..78b0ed28cd 100644 --- a/web/i18n/tr-TR/common.json +++ b/web/i18n/tr-TR/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "İndirme Tamamlandı.", "operation.duplicate": "Çoğalt", "operation.edit": "Düzenle", + "operation.exporting": "Dışa aktarılıyor", "operation.format": "Format", "operation.getForFree": "Ücretsiz edinin", "operation.imageCopied": "Kopyalanan görüntü", diff --git a/web/i18n/uk-UA/common.json b/web/i18n/uk-UA/common.json index c0cc746db1..e3892099d6 100644 --- a/web/i18n/uk-UA/common.json +++ b/web/i18n/uk-UA/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Завантаження завершено.", "operation.duplicate": "дублікат", "operation.edit": "Редагувати", + "operation.exporting": "Експорт", "operation.format": "Формат", "operation.getForFree": "Отримати безкоштовно", "operation.imageCopied": "Скопійоване зображення", diff --git a/web/i18n/vi-VN/common.json b/web/i18n/vi-VN/common.json index 8ce4211cda..cd8e9dd6fb 100644 --- a/web/i18n/vi-VN/common.json +++ b/web/i18n/vi-VN/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "Tải xuống đã hoàn thành.", "operation.duplicate": "Nhân bản", "operation.edit": "Chỉnh sửa", + "operation.exporting": "Đang xuất", "operation.format": "Định dạng", "operation.getForFree": "Nhận miễn phí", "operation.imageCopied": "Hình ảnh sao chép", diff --git a/web/i18n/zh-Hans/common.json b/web/i18n/zh-Hans/common.json index 924142e36f..b2bdf8f1f1 100644 --- a/web/i18n/zh-Hans/common.json +++ b/web/i18n/zh-Hans/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "下载完毕", "operation.duplicate": "复制", "operation.edit": "编辑", + "operation.exporting": "导出中", "operation.format": "格式化", "operation.getForFree": "免费获取", "operation.imageCopied": "图片已复制", diff --git a/web/i18n/zh-Hant/common.json b/web/i18n/zh-Hant/common.json index ec8e78cf81..e6ee39bc47 100644 --- a/web/i18n/zh-Hant/common.json +++ b/web/i18n/zh-Hant/common.json @@ -494,6 +494,7 @@ "operation.downloadSuccess": "下載完成。", "operation.duplicate": "複製", "operation.edit": "編輯", + "operation.exporting": "匯出中", "operation.format": "格式", "operation.getForFree": "免費獲取", "operation.imageCopied": "複製的圖片", From e5617888091decde4be5003bcf708448d362fbec Mon Sep 17 00:00:00 2001 From: zyssyz123 <916125788@qq.com> Date: Mon, 20 Apr 2026 18:10:03 +0800 Subject: [PATCH 007/253] fix(auth): enforce phase-bound change-email token flow (GHSA-4q3w-q5mc-45rq) (#35425) --- api/controllers/console/workspace/account.py | 57 ++- api/services/account_service.py | 17 +- .../console/test_workspace_account.py | 372 +++++++++++++++++- 3 files changed, 435 insertions(+), 11 deletions(-) diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index 44404005b2..c01286cc59 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -595,13 +595,25 @@ class ChangeEmailSendEmailApi(Resource): account = None user_email = None email_for_sending = args.email.lower() - if args.phase is not None and args.phase == "new_email": + # Default to the initial phase; any legacy/unexpected client input is + # coerced back to `old_email` so we never trust the caller to declare + # later phases without a verified predecessor token. + send_phase = AccountService.CHANGE_EMAIL_PHASE_OLD + if args.phase is not None and args.phase == AccountService.CHANGE_EMAIL_PHASE_NEW: + send_phase = AccountService.CHANGE_EMAIL_PHASE_NEW if args.token is None: raise InvalidTokenError() reset_data = AccountService.get_change_email_data(args.token) if reset_data is None: raise InvalidTokenError() + + # The token used to request a new-email code must come from the + # old-email verification step. This prevents the bypass described + # in GHSA-4q3w-q5mc-45rq where the phase-1 token was reused here. + token_phase = reset_data.get(AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY) + if token_phase != AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED: + raise InvalidTokenError() user_email = reset_data.get("email", "") if user_email.lower() != current_user.email.lower(): @@ -620,7 +632,7 @@ class ChangeEmailSendEmailApi(Resource): email=email_for_sending, old_email=user_email, language=language, - phase=args.phase, + phase=send_phase, ) return {"result": "success", "data": token} @@ -655,12 +667,31 @@ class ChangeEmailCheckApi(Resource): AccountService.add_change_email_error_rate_limit(user_email) raise EmailCodeError() + # Only advance tokens that were minted by the matching send-code step; + # refuse tokens that have already progressed or lack a phase marker so + # the chain `old_email -> old_email_verified -> new_email -> new_email_verified` + # is strictly enforced. + phase_transitions = { + AccountService.CHANGE_EMAIL_PHASE_OLD: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED, + AccountService.CHANGE_EMAIL_PHASE_NEW: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED, + } + token_phase = token_data.get(AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY) + if not isinstance(token_phase, str): + raise InvalidTokenError() + refreshed_phase = phase_transitions.get(token_phase) + if refreshed_phase is None: + raise InvalidTokenError() + # Verified, revoke the first token AccountService.revoke_change_email_token(args.token) - # Refresh token data by generating a new token + # Refresh token data by generating a new token that carries the + # upgraded phase so later steps can check it. _, new_token = AccountService.generate_change_email_token( - user_email, code=args.code, old_email=token_data.get("old_email"), additional_data={} + user_email, + code=args.code, + old_email=token_data.get("old_email"), + additional_data={AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: refreshed_phase}, ) AccountService.reset_change_email_error_rate_limit(user_email) @@ -690,13 +721,29 @@ class ChangeEmailResetApi(Resource): if not reset_data: raise InvalidTokenError() - AccountService.revoke_change_email_token(args.token) + # Only tokens that completed both verification phases may be used to + # change the email. This closes GHSA-4q3w-q5mc-45rq where a token from + # the initial send-code step could be replayed directly here. + token_phase = reset_data.get(AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY) + if token_phase != AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED: + raise InvalidTokenError() + + # Bind the new email to the token that was mailed and verified, so a + # verified token cannot be reused with a different `new_email` value. + token_email = reset_data.get("email") + normalized_token_email = token_email.lower() if isinstance(token_email, str) else token_email + if normalized_token_email != normalized_new_email: + raise InvalidTokenError() old_email = reset_data.get("old_email", "") current_user, _ = current_account_with_tenant() if current_user.email.lower() != old_email.lower(): raise AccountNotFound() + # Revoke only after all checks pass so failed attempts don't burn a + # legitimately verified token. + AccountService.revoke_change_email_token(args.token) + updated_account = AccountService.update_account_email(current_user, email=normalized_new_email) AccountService.send_change_email_completed_notify_email( diff --git a/api/services/account_service.py b/api/services/account_service.py index ccc4a7c1fa..b6554a3de7 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -112,6 +112,14 @@ REFRESH_TOKEN_EXPIRY = timedelta(days=dify_config.REFRESH_TOKEN_EXPIRE_DAYS) class AccountService: + # Phase-bound token metadata for the change-email flow. Tokens carry the + # current phase so that downstream endpoints can enforce proper progression + CHANGE_EMAIL_TOKEN_PHASE_KEY = "email_change_phase" + CHANGE_EMAIL_PHASE_OLD = "old_email" + CHANGE_EMAIL_PHASE_OLD_VERIFIED = "old_email_verified" + CHANGE_EMAIL_PHASE_NEW = "new_email" + CHANGE_EMAIL_PHASE_NEW_VERIFIED = "new_email_verified" + reset_password_rate_limiter = RateLimiter(prefix="reset_password_rate_limit", max_attempts=1, time_window=60 * 1) email_register_rate_limiter = RateLimiter(prefix="email_register_rate_limit", max_attempts=1, time_window=60 * 1) email_code_login_rate_limiter = RateLimiter( @@ -576,13 +584,20 @@ class AccountService: raise ValueError("Email must be provided.") if not phase: raise ValueError("phase must be provided.") + if phase not in (cls.CHANGE_EMAIL_PHASE_OLD, cls.CHANGE_EMAIL_PHASE_NEW): + raise ValueError("phase must be one of old_email or new_email.") if cls.change_email_rate_limiter.is_rate_limited(account_email): from controllers.console.auth.error import EmailChangeRateLimitExceededError raise EmailChangeRateLimitExceededError(int(cls.change_email_rate_limiter.time_window / 60)) - code, token = cls.generate_change_email_token(account_email, account, old_email=old_email) + code, token = cls.generate_change_email_token( + account_email, + account, + old_email=old_email, + additional_data={cls.CHANGE_EMAIL_TOKEN_PHASE_KEY: phase}, + ) send_change_mail_task.delay( language=language, diff --git a/api/tests/unit_tests/controllers/console/test_workspace_account.py b/api/tests/unit_tests/controllers/console/test_workspace_account.py index c513be950b..26ff264f18 100644 --- a/api/tests/unit_tests/controllers/console/test_workspace_account.py +++ b/api/tests/unit_tests/controllers/console/test_workspace_account.py @@ -68,7 +68,10 @@ class TestChangeEmailSend: mock_features.return_value = SimpleNamespace(enable_change_email=True) mock_account = _build_account("current@example.com", "acc1") mock_current_account.return_value = (mock_account, None) - mock_get_change_data.return_value = {"email": "current@example.com"} + mock_get_change_data.return_value = { + "email": "current@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED, + } mock_send_email.return_value = "token-abc" with app.test_request_context( @@ -85,12 +88,55 @@ class TestChangeEmailSend: email="new@example.com", old_email="current@example.com", language="en-US", - phase="new_email", + phase=AccountService.CHANGE_EMAIL_PHASE_NEW, ) mock_extract_ip.assert_called_once() mock_is_ip_limit.assert_called_once_with("127.0.0.1") mock_csrf.assert_called_once() + @patch("controllers.console.wraps.db") + @patch("controllers.console.workspace.account.current_account_with_tenant") + @patch("controllers.console.workspace.account.AccountService.get_change_email_data") + @patch("controllers.console.workspace.account.AccountService.send_change_email_email") + @patch("controllers.console.workspace.account.AccountService.is_email_send_ip_limit", return_value=False) + @patch("controllers.console.workspace.account.extract_remote_ip", return_value="127.0.0.1") + @patch("libs.login.check_csrf_token", return_value=None) + @patch("controllers.console.wraps.FeatureService.get_system_features") + def test_should_reject_new_email_phase_when_token_phase_is_not_old_verified( + self, + mock_features, + mock_csrf, + mock_extract_ip, + mock_is_ip_limit, + mock_send_email, + mock_get_change_data, + mock_current_account, + mock_db, + app, + ): + """GHSA-4q3w-q5mc-45rq: a phase-1 token must not unlock the new-email send step.""" + from controllers.console.auth.error import InvalidTokenError + + _mock_wraps_db(mock_db) + mock_features.return_value = SimpleNamespace(enable_change_email=True) + mock_account = _build_account("current@example.com", "acc1") + mock_current_account.return_value = (mock_account, None) + mock_get_change_data.return_value = { + "email": "current@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD, + } + + with app.test_request_context( + "/account/change-email", + method="POST", + json={"email": "New@Example.com", "language": "en-US", "phase": "new_email", "token": "token-123"}, + ): + _set_logged_in_user(_build_account("tester@example.com", "tester")) + with pytest.raises(InvalidTokenError): + ChangeEmailSendEmailApi().post() + + mock_send_email.assert_not_called() + class TestChangeEmailValidity: @patch("controllers.console.wraps.db") @@ -122,7 +168,12 @@ class TestChangeEmailValidity: mock_account = _build_account("user@example.com", "acc2") mock_current_account.return_value = (mock_account, None) mock_is_rate_limit.return_value = False - mock_get_data.return_value = {"email": "user@example.com", "code": "1234", "old_email": "old@example.com"} + mock_get_data.return_value = { + "email": "user@example.com", + "code": "1234", + "old_email": "old@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD, + } mock_generate_token.return_value = (None, "new-token") with app.test_request_context( @@ -138,11 +189,169 @@ class TestChangeEmailValidity: mock_add_rate.assert_not_called() mock_revoke_token.assert_called_once_with("token-123") mock_generate_token.assert_called_once_with( - "user@example.com", code="1234", old_email="old@example.com", additional_data={} + "user@example.com", + code="1234", + old_email="old@example.com", + additional_data={ + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD_VERIFIED, + }, ) mock_reset_rate.assert_called_once_with("user@example.com") mock_csrf.assert_called_once() + @patch("controllers.console.wraps.db") + @patch("controllers.console.workspace.account.current_account_with_tenant") + @patch("controllers.console.workspace.account.AccountService.reset_change_email_error_rate_limit") + @patch("controllers.console.workspace.account.AccountService.generate_change_email_token") + @patch("controllers.console.workspace.account.AccountService.revoke_change_email_token") + @patch("controllers.console.workspace.account.AccountService.add_change_email_error_rate_limit") + @patch("controllers.console.workspace.account.AccountService.get_change_email_data") + @patch("controllers.console.workspace.account.AccountService.is_change_email_error_rate_limit") + @patch("libs.login.check_csrf_token", return_value=None) + @patch("controllers.console.wraps.FeatureService.get_system_features") + def test_should_upgrade_new_phase_token_to_new_verified( + self, + mock_features, + mock_csrf, + mock_is_rate_limit, + mock_get_data, + mock_add_rate, + mock_revoke_token, + mock_generate_token, + mock_reset_rate, + mock_current_account, + mock_db, + app, + ): + _mock_wraps_db(mock_db) + mock_features.return_value = SimpleNamespace(enable_change_email=True) + mock_current_account.return_value = (_build_account("old@example.com", "acc"), None) + mock_is_rate_limit.return_value = False + mock_get_data.return_value = { + "email": "new@example.com", + "code": "1234", + "old_email": "old@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW, + } + mock_generate_token.return_value = (None, "new-verified-token") + + with app.test_request_context( + "/account/change-email/validity", + method="POST", + json={"email": "new@example.com", "code": "1234", "token": "token-123"}, + ): + _set_logged_in_user(_build_account("tester@example.com", "tester")) + response = ChangeEmailCheckApi().post() + + assert response == {"is_valid": True, "email": "new@example.com", "token": "new-verified-token"} + mock_generate_token.assert_called_once_with( + "new@example.com", + code="1234", + old_email="old@example.com", + additional_data={ + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED, + }, + ) + + @patch("controllers.console.wraps.db") + @patch("controllers.console.workspace.account.current_account_with_tenant") + @patch("controllers.console.workspace.account.AccountService.reset_change_email_error_rate_limit") + @patch("controllers.console.workspace.account.AccountService.generate_change_email_token") + @patch("controllers.console.workspace.account.AccountService.revoke_change_email_token") + @patch("controllers.console.workspace.account.AccountService.add_change_email_error_rate_limit") + @patch("controllers.console.workspace.account.AccountService.get_change_email_data") + @patch("controllers.console.workspace.account.AccountService.is_change_email_error_rate_limit") + @patch("libs.login.check_csrf_token", return_value=None) + @patch("controllers.console.wraps.FeatureService.get_system_features") + def test_should_reject_validity_when_token_phase_is_unknown( + self, + mock_features, + mock_csrf, + mock_is_rate_limit, + mock_get_data, + mock_add_rate, + mock_revoke_token, + mock_generate_token, + mock_reset_rate, + mock_current_account, + mock_db, + app, + ): + """A token whose phase marker is a string but not a known transition must be rejected.""" + from controllers.console.auth.error import InvalidTokenError + + _mock_wraps_db(mock_db) + mock_features.return_value = SimpleNamespace(enable_change_email=True) + mock_current_account.return_value = (_build_account("old@example.com", "acc"), None) + mock_is_rate_limit.return_value = False + mock_get_data.return_value = { + "email": "user@example.com", + "code": "1234", + "old_email": "old@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: "something_else", + } + + with app.test_request_context( + "/account/change-email/validity", + method="POST", + json={"email": "user@example.com", "code": "1234", "token": "token-123"}, + ): + _set_logged_in_user(_build_account("tester@example.com", "tester")) + with pytest.raises(InvalidTokenError): + ChangeEmailCheckApi().post() + + mock_revoke_token.assert_not_called() + mock_generate_token.assert_not_called() + + @patch("controllers.console.wraps.db") + @patch("controllers.console.workspace.account.current_account_with_tenant") + @patch("controllers.console.workspace.account.AccountService.reset_change_email_error_rate_limit") + @patch("controllers.console.workspace.account.AccountService.generate_change_email_token") + @patch("controllers.console.workspace.account.AccountService.revoke_change_email_token") + @patch("controllers.console.workspace.account.AccountService.add_change_email_error_rate_limit") + @patch("controllers.console.workspace.account.AccountService.get_change_email_data") + @patch("controllers.console.workspace.account.AccountService.is_change_email_error_rate_limit") + @patch("libs.login.check_csrf_token", return_value=None) + @patch("controllers.console.wraps.FeatureService.get_system_features") + def test_should_reject_validity_when_token_has_no_phase( + self, + mock_features, + mock_csrf, + mock_is_rate_limit, + mock_get_data, + mock_add_rate, + mock_revoke_token, + mock_generate_token, + mock_reset_rate, + mock_current_account, + mock_db, + app, + ): + """A token minted without a phase marker (e.g. a hand-crafted token) must not validate.""" + from controllers.console.auth.error import InvalidTokenError + + _mock_wraps_db(mock_db) + mock_features.return_value = SimpleNamespace(enable_change_email=True) + mock_current_account.return_value = (_build_account("old@example.com", "acc"), None) + mock_is_rate_limit.return_value = False + mock_get_data.return_value = { + "email": "user@example.com", + "code": "1234", + "old_email": "old@example.com", + } + + with app.test_request_context( + "/account/change-email/validity", + method="POST", + json={"email": "user@example.com", "code": "1234", "token": "token-123"}, + ): + _set_logged_in_user(_build_account("tester@example.com", "tester")) + with pytest.raises(InvalidTokenError): + ChangeEmailCheckApi().post() + + mock_revoke_token.assert_not_called() + mock_generate_token.assert_not_called() + class TestChangeEmailReset: @patch("controllers.console.wraps.db") @@ -175,7 +384,11 @@ class TestChangeEmailReset: mock_current_account.return_value = (current_user, None) mock_is_freeze.return_value = False mock_check_unique.return_value = True - mock_get_data.return_value = {"old_email": "OLD@example.com"} + mock_get_data.return_value = { + "email": "new@example.com", + "old_email": "OLD@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED, + } mock_account_after_update = _build_account("new@example.com", "acc3-updated") mock_update_account.return_value = mock_account_after_update @@ -194,6 +407,155 @@ class TestChangeEmailReset: mock_send_notify.assert_called_once_with(email="new@example.com") mock_csrf.assert_called_once() + @patch("controllers.console.wraps.db") + @patch("controllers.console.workspace.account.current_account_with_tenant") + @patch("controllers.console.workspace.account.AccountService.send_change_email_completed_notify_email") + @patch("controllers.console.workspace.account.AccountService.update_account_email") + @patch("controllers.console.workspace.account.AccountService.revoke_change_email_token") + @patch("controllers.console.workspace.account.AccountService.get_change_email_data") + @patch("controllers.console.workspace.account.AccountService.check_email_unique") + @patch("controllers.console.workspace.account.AccountService.is_account_in_freeze") + @patch("libs.login.check_csrf_token", return_value=None) + @patch("controllers.console.wraps.FeatureService.get_system_features") + def test_should_reject_reset_when_token_phase_is_not_new_verified( + self, + mock_features, + mock_csrf, + mock_is_freeze, + mock_check_unique, + mock_get_data, + mock_revoke_token, + mock_update_account, + mock_send_notify, + mock_current_account, + mock_db, + app, + ): + """GHSA-4q3w-q5mc-45rq PoC: phase-1 token must not be usable against /reset.""" + from controllers.console.auth.error import InvalidTokenError + + _mock_wraps_db(mock_db) + mock_features.return_value = SimpleNamespace(enable_change_email=True) + current_user = _build_account("old@example.com", "acc3") + mock_current_account.return_value = (current_user, None) + mock_is_freeze.return_value = False + mock_check_unique.return_value = True + # Simulate a token straight out of step #1 (phase=old_email) — exactly + # the replay used in the advisory PoC. + mock_get_data.return_value = { + "email": "old@example.com", + "old_email": "old@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_OLD, + } + + with app.test_request_context( + "/account/change-email/reset", + method="POST", + json={"new_email": "attacker@example.com", "token": "token-from-step1"}, + ): + _set_logged_in_user(_build_account("tester@example.com", "tester")) + with pytest.raises(InvalidTokenError): + ChangeEmailResetApi().post() + + mock_revoke_token.assert_not_called() + mock_update_account.assert_not_called() + mock_send_notify.assert_not_called() + + @patch("controllers.console.wraps.db") + @patch("controllers.console.workspace.account.current_account_with_tenant") + @patch("controllers.console.workspace.account.AccountService.send_change_email_completed_notify_email") + @patch("controllers.console.workspace.account.AccountService.update_account_email") + @patch("controllers.console.workspace.account.AccountService.revoke_change_email_token") + @patch("controllers.console.workspace.account.AccountService.get_change_email_data") + @patch("controllers.console.workspace.account.AccountService.check_email_unique") + @patch("controllers.console.workspace.account.AccountService.is_account_in_freeze") + @patch("libs.login.check_csrf_token", return_value=None) + @patch("controllers.console.wraps.FeatureService.get_system_features") + def test_should_reject_reset_when_token_email_differs_from_payload_new_email( + self, + mock_features, + mock_csrf, + mock_is_freeze, + mock_check_unique, + mock_get_data, + mock_revoke_token, + mock_update_account, + mock_send_notify, + mock_current_account, + mock_db, + app, + ): + """A verified token for address A must not be replayed to change to address B.""" + from controllers.console.auth.error import InvalidTokenError + + _mock_wraps_db(mock_db) + mock_features.return_value = SimpleNamespace(enable_change_email=True) + current_user = _build_account("old@example.com", "acc3") + mock_current_account.return_value = (current_user, None) + mock_is_freeze.return_value = False + mock_check_unique.return_value = True + mock_get_data.return_value = { + "email": "verified@example.com", + "old_email": "old@example.com", + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW_VERIFIED, + } + + with app.test_request_context( + "/account/change-email/reset", + method="POST", + json={"new_email": "attacker@example.com", "token": "token-verified"}, + ): + _set_logged_in_user(_build_account("tester@example.com", "tester")) + with pytest.raises(InvalidTokenError): + ChangeEmailResetApi().post() + + mock_revoke_token.assert_not_called() + mock_update_account.assert_not_called() + mock_send_notify.assert_not_called() + + +class TestAccountServiceSendChangeEmailEmail: + """Service-level coverage for the phase-bound changes in `send_change_email_email`.""" + + def test_should_raise_value_error_for_invalid_phase(self): + with pytest.raises(ValueError, match="phase must be one of"): + AccountService.send_change_email_email( + email="user@example.com", + old_email="user@example.com", + phase="old_email_verified", + ) + + @patch("services.account_service.send_change_mail_task") + @patch("services.account_service.AccountService.change_email_rate_limiter") + @patch("services.account_service.AccountService.generate_change_email_token") + def test_should_stamp_phase_into_generated_token( + self, + mock_generate_token, + mock_rate_limiter, + mock_mail_task, + ): + mock_rate_limiter.is_rate_limited.return_value = False + mock_generate_token.return_value = ("123456", "the-token") + + returned = AccountService.send_change_email_email( + email="user@example.com", + old_email="user@example.com", + language="en-US", + phase=AccountService.CHANGE_EMAIL_PHASE_NEW, + ) + + assert returned == "the-token" + mock_generate_token.assert_called_once_with( + "user@example.com", + None, + old_email="user@example.com", + additional_data={ + AccountService.CHANGE_EMAIL_TOKEN_PHASE_KEY: AccountService.CHANGE_EMAIL_PHASE_NEW, + }, + ) + mock_mail_task.delay.assert_called_once() + mock_rate_limiter.increment_rate_limit.assert_called_once_with("user@example.com") + class TestAccountDeletionFeedback: @patch("controllers.console.wraps.db") From db60e649b8d0f60eb85424334db2ecddc54d225a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:21:57 +0900 Subject: [PATCH 008/253] chore(deps): bump json-repair from 0.59.2 to 0.59.4 in /api in the python-packages group (#35404) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/pyproject.toml | 2 +- api/uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 159b09d844..1677a6029d 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "fastopenapi[flask]~=0.7.0", "graphon~=0.2.2", "httpx-sse~=0.4.0", - "json-repair~=0.59.2", + "json-repair~=0.59.4", ] # Before adding new dependency, consider place it in # alphabet order (a-z) and suitable group. diff --git a/api/uv.lock b/api/uv.lock index 226cb96e4c..c464e91280 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1596,7 +1596,7 @@ requires-dist = [ { name = "gunicorn", specifier = ">=25.3.0" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<1.0.0" }, { name = "httpx-sse", specifier = "~=0.4.0" }, - { name = "json-repair", specifier = "~=0.59.2" }, + { name = "json-repair", specifier = "~=0.59.4" }, { name = "opentelemetry-distro", specifier = ">=0.62b0,<1.0.0" }, { name = "opentelemetry-instrumentation-celery", specifier = ">=0.62b0,<1.0.0" }, { name = "opentelemetry-instrumentation-flask", specifier = ">=0.62b0,<1.0.0" }, @@ -3492,11 +3492,11 @@ wheels = [ [[package]] name = "json-repair" -version = "0.59.2" +version = "0.59.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/cb/a49f1661737a78098ce33668350590c981a4163055bc9a01e0cc688d896a/json_repair-0.59.2.tar.gz", hash = "sha256:1d8abb2fa94c4035a66ef9892ea3785dace8dcf09c583e6de781cfd31b278b3d", size = 48341, upload-time = "2026-04-11T15:55:41.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/41/4ae9c6e711647a41b4e0c04bce815113ce9c0286eff6dc6fb86979b2fb9f/json_repair-0.59.4.tar.gz", hash = "sha256:559ca1828f6f566530663cd96d64bee29f8282b9d2ff0e661e05fa87b4171ab3", size = 47624, upload-time = "2026-04-15T06:48:40.776Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/03/7afcecb4242d93b684708b47fb014abdc1922a01b38c0e30f1117ae74a83/json_repair-0.59.2-py3-none-any.whl", hash = "sha256:6ca6238519c24f671bcb05d1f38a0d6a452bb4ca5af82137595c5c2f1a0fb785", size = 46918, upload-time = "2026-04-11T15:55:39.817Z" }, + { url = "https://files.pythonhosted.org/packages/74/c4/ec3068436d2275731539b7a43fbc947f502bc3fe149856a5d00368c7b087/json_repair-0.59.4-py3-none-any.whl", hash = "sha256:46052e646bc0b0c39db672ebbf732f774f3c1a5bde81a54f0b0e19d3af4f45cd", size = 46697, upload-time = "2026-04-15T06:48:39.61Z" }, ] [[package]] From 21e5962f98fe6bac29d5f52ab441908b3bf30db3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:22:13 +0000 Subject: [PATCH 009/253] chore(deps-dev): bump xinference-client from 2.4.0 to 2.5.0 in /api in the vdb group (#35399) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/pyproject.toml | 4 ++-- api/uv.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 1677a6029d..69dfda900b 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -174,7 +174,7 @@ dev = [ "pytest-timeout>=2.4.0", "pytest-xdist>=3.8.0", "pyrefly>=0.61.1", - "xinference-client>=2.4.0", + "xinference-client>=2.5.0", ] ############################################################ @@ -266,7 +266,7 @@ vdb-vastbase = ["dify-vdb-vastbase"] vdb-vikingdb = ["dify-vdb-vikingdb"] vdb-weaviate = ["dify-vdb-weaviate"] # Optional client used by some tests / integrations (not a vector backend plugin) -vdb-xinference = ["xinference-client>=2.4.0"] +vdb-xinference = ["xinference-client>=2.5.0"] trace-all = [ "dify-trace-aliyun", diff --git a/api/uv.lock b/api/uv.lock index c464e91280..7e7324f60c 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1677,7 +1677,7 @@ dev = [ { name = "types-tensorflow", specifier = ">=2.18.0.20260408" }, { name = "types-tqdm", specifier = ">=4.67.3.20260408" }, { name = "types-ujson", specifier = ">=5.10.0" }, - { name = "xinference-client", specifier = ">=2.4.0" }, + { name = "xinference-client", specifier = ">=2.5.0" }, ] storage = [ { name = "azure-storage-blob", specifier = ">=12.28.0" }, @@ -1774,7 +1774,7 @@ vdb-upstash = [{ name = "dify-vdb-upstash", editable = "providers/vdb/vdb-upstas vdb-vastbase = [{ name = "dify-vdb-vastbase", editable = "providers/vdb/vdb-vastbase" }] vdb-vikingdb = [{ name = "dify-vdb-vikingdb", editable = "providers/vdb/vdb-vikingdb" }] vdb-weaviate = [{ name = "dify-vdb-weaviate", editable = "providers/vdb/vdb-weaviate" }] -vdb-xinference = [{ name = "xinference-client", specifier = ">=2.4.0" }] +vdb-xinference = [{ name = "xinference-client", specifier = ">=2.5.0" }] [[package]] name = "dify-trace-aliyun" @@ -7413,7 +7413,7 @@ wheels = [ [[package]] name = "xinference-client" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -7421,9 +7421,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/f2/7640528fd4f816df19afe91d52332a658ad2d2bacb13471b0a27dbd0cf46/xinference_client-2.4.0.tar.gz", hash = "sha256:59de6d58f89126c8ff05136818e0756108e534858255d7c4c0673b804fd2d01d", size = 58386, upload-time = "2026-03-29T05:10:58.533Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/8a/4d7c72510f3c462195c2e7aa63559cafcf20f7d1901132d533b7498bab1c/xinference_client-2.5.0.tar.gz", hash = "sha256:0680324e2f438b8b208ca80e8a7e1c22e9152fce54f8c024c75e2ce57bfa5639", size = 58430, upload-time = "2026-04-13T07:21:40.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cf/9d27e0095cc28691c73ff186b33556790c7b87f046ca2ecd517c80272592/xinference_client-2.4.0-py3-none-any.whl", hash = "sha256:2f9478b00fe15643f281fe4c0643e74479c8b7837d377000ff120702cda81efc", size = 40012, upload-time = "2026-03-29T05:10:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/5b/dd/4fd501b8092c01f0775142850e3b601d743edf733077b756defe4a01cc37/xinference_client-2.5.0-py3-none-any.whl", hash = "sha256:bb90f069a2c30ac6ea7453ab37a0fadd34c28b655afa51fe20c18e67a361c269", size = 40006, upload-time = "2026-04-13T07:21:38.851Z" }, ] [[package]] From de123a8695fd56e8d765583928f4e97b3e7fefa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:22:22 +0000 Subject: [PATCH 010/253] chore(deps): bump google-cloud-aiplatform from 1.147.0 to 1.148.1 in /api in the google group (#35397) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/pyproject.toml | 2 +- api/uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 69dfda900b..d106ac845e 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "flask-migrate>=4.1.0,<5.0.0", "flask-orjson>=2.0.0,<3.0.0", "flask-restx>=1.3.2,<2.0.0", - "google-cloud-aiplatform>=1.147.0,<2.0.0", + "google-cloud-aiplatform>=1.148.1,<2.0.0", "httpx[socks]>=0.28.1,<1.0.0", "opentelemetry-distro>=0.62b0,<1.0.0", "opentelemetry-instrumentation-celery>=0.62b0,<1.0.0", diff --git a/api/uv.lock b/api/uv.lock index 7e7324f60c..0f10b67926 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1591,7 +1591,7 @@ requires-dist = [ { name = "gevent-websocket", specifier = ">=0.10.1" }, { name = "gmpy2", specifier = ">=2.3.0" }, { name = "google-api-python-client", specifier = ">=2.194.0" }, - { name = "google-cloud-aiplatform", specifier = ">=1.147.0,<2.0.0" }, + { name = "google-cloud-aiplatform", specifier = ">=1.148.1,<2.0.0" }, { name = "graphon", specifier = "~=0.2.2" }, { name = "gunicorn", specifier = ">=25.3.0" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<1.0.0" }, @@ -2764,7 +2764,7 @@ wheels = [ [[package]] name = "google-cloud-aiplatform" -version = "1.147.0" +version = "1.148.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docstring-parser" }, @@ -2780,9 +2780,9 @@ dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/93/9bfcaaf1ceab12999a881ccf69ebd9b30f467ec5623989c66894e81fc139/google_cloud_aiplatform-1.147.0.tar.gz", hash = "sha256:b2e1b669ba37f02426e03eb13187eebf4cbfeaa0a3bfed37b5578abb375ab689", size = 10235245, upload-time = "2026-04-09T17:14:49.179Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/f3/b2a9417014c93858a2e3266134f931eefd972c2d410b25d7b8782fc6f143/google_cloud_aiplatform-1.148.1.tar.gz", hash = "sha256:75d605fba34e68714bd08e1e482755d0a6e3ae972805f809d088e686c30879e7", size = 10278758, upload-time = "2026-04-17T23:45:26.738Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/d2/1c1c582f6bbed9bbc0daa5acf3a5d98751ca8bc48584548d28569b8ce1a7/google_cloud_aiplatform-1.147.0-py2.py3-none-any.whl", hash = "sha256:29f7ae020718d3c45094f0475464e06a97f81b1572bea150ae6a1b22c5f45997", size = 8408951, upload-time = "2026-04-09T17:14:45.482Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/e3515d7bbba602c2b0f6a0da5431785e897252443682e4735d0e6873dc8f/google_cloud_aiplatform-1.148.1-py2.py3-none-any.whl", hash = "sha256:035101e2d8e65c6a706cc3930b2452de7ddcbde50dd130320fcea0d8b03b0c5a", size = 8434481, upload-time = "2026-04-17T23:45:22.919Z" }, ] [[package]] From ba8e0681d5368f0bb27906d119230925c5f8cb3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:22:27 +0000 Subject: [PATCH 011/253] chore(deps): bump the storage group in /api with 3 updates (#35398) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/pyproject.toml | 6 +- api/uv.lock | 136 +++++++++++++++++++++++++++++++++------------ 2 files changed, 104 insertions(+), 38 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index d106ac845e..b13c744f0a 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -6,7 +6,7 @@ requires-python = "~=3.12.0" dependencies = [ # Legacy: mature and widely deployed "bleach>=6.3.0", - "boto3>=1.42.88", + "boto3>=1.42.91", "celery>=5.6.3", "croniter>=6.2.2", "flask-cors>=6.0.2", @@ -183,13 +183,13 @@ dev = [ ############################################################ storage = [ "azure-storage-blob>=12.28.0", - "bce-python-sdk>=0.9.69", + "bce-python-sdk>=0.9.70", "cos-python-sdk-v5>=1.9.41", "esdk-obs-python>=3.22.2", "google-cloud-storage>=3.10.1", "opendal>=0.46.0", "oss2>=2.19.1", - "supabase>=2.18.1", + "supabase>=2.28.3", "tos>=2.9.0", ] diff --git a/api/uv.lock b/api/uv.lock index 0f10b67926..40d196a160 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -481,7 +481,7 @@ wheels = [ [[package]] name = "bce-python-sdk" -version = "0.9.69" +version = "0.9.70" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "crc32c" }, @@ -489,9 +489,9 @@ dependencies = [ { name = "pycryptodome" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/9c/8fdaf7f9259002b5aa9101bb88252f6d05f65c4535bbca567457da84d765/bce_python_sdk-0.9.69.tar.gz", hash = "sha256:2aaa9f4fc118b3efb720a66d7a541789b7d838a1ddacb9f3c6faa6b75e1c7d23", size = 300008, upload-time = "2026-04-10T08:13:29.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/a9/7c21a9073eb9ad7e8cacf6f8a0e47c0d01ad7bf8fd8e0dc42164b117d60b/bce_python_sdk-0.9.70.tar.gz", hash = "sha256:3b37fd7448278dd33f745a6a23198a2cc2490fded9cb8d59b72500784853df4e", size = 299967, upload-time = "2026-04-14T12:02:42.034Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/3b/41c2985d1b3b3bd5cdf103b4156b08320268ee7a0617f2a40c34fdd377e9/bce_python_sdk-0.9.69-py3-none-any.whl", hash = "sha256:50fb94833b5f4931255296396081b85143101bd9a7a894efbf20d1f759779de5", size = 415659, upload-time = "2026-04-10T08:13:27.958Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2d/70fc866ff98d1f6bd75b0a4235694129b3c519b014254d7bcfc02ffe1bee/bce_python_sdk-0.9.70-py3-none-any.whl", hash = "sha256:fd1f31113e4a8dca314f040662b7caf07ec11cf896c5da232627a9a2c9d2e3a1", size = 415660, upload-time = "2026-04-14T12:02:40.034Z" }, ] [[package]] @@ -604,16 +604,16 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.88" +version = "1.42.91" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/bb/7d4435cca6fccf235dd40c891c731bcb9078e815917b57ebadd1e0ffabaf/boto3-1.42.88.tar.gz", hash = "sha256:2d22c70de5726918676a06f1a03acfb4d5d9ea92fc759354800b67b22aaeef19", size = 113238, upload-time = "2026-04-10T19:41:06.912Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/c0/98b8cec7ca22dde776df48c58940ae1abc425593959b7226e270760d726f/boto3-1.42.91.tar.gz", hash = "sha256:03d70532b17f7f84df37ca7e8c21553280454dea53ae12b15d1cfef9b16fcb8a", size = 113181, upload-time = "2026-04-17T19:31:06.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/2b/8bfddb39a19f5fbc16a869f1a394771e6223f07160dbc0ff6b38e05ea0ae/boto3-1.42.88-py3-none-any.whl", hash = "sha256:2d0f52c971503377e4370d2a83edee6f077ddb8e684366ff38df4f13581d9cfc", size = 140557, upload-time = "2026-04-10T19:41:05.309Z" }, + { url = "https://files.pythonhosted.org/packages/02/29/faba6521257c34085cc9b439ef98235b581772580f417fa3629728007270/boto3-1.42.91-py3-none-any.whl", hash = "sha256:04e72071cde022951ce7f81bd9933c90095ab8923e8ced61c8dacfe9edac0f5c", size = 140553, upload-time = "2026-04-17T19:31:02.57Z" }, ] [[package]] @@ -636,16 +636,16 @@ bedrock-runtime = [ [[package]] name = "botocore" -version = "1.42.88" +version = "1.42.91" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/50/87966238f7aa3f7e5f87081185d5a407a95ede8b551e11bbe134ca3306dc/botocore-1.42.88.tar.gz", hash = "sha256:cbb59ee464662039b0c2c95a520cdf85b1e8ce00b72375ab9cd9f842cc001301", size = 15195331, upload-time = "2026-04-10T19:40:57.012Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/bc/a4b7c46471c2e789ad8c4c7acfd7f302fdb481d93ff870f441249b924ae6/botocore-1.42.91.tar.gz", hash = "sha256:d252e27bc454afdbf5ed3dc617aa423f2c855c081e98b7963093399483ecc698", size = 15213010, upload-time = "2026-04-17T19:30:50.793Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/46/ad14e41245adb8b0c83663ba13e822b68a0df08999dd250e75b0750fdf6c/botocore-1.42.88-py3-none-any.whl", hash = "sha256:032375b213305b6b81eedb269eaeefdf96f674620799bbf96117dca86052cc1a", size = 14876640, upload-time = "2026-04-10T19:40:53.663Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fc/24cc0a47c824f13933e210e9ad034b4fba22f7185b8d904c0fbf5a3b2be8/botocore-1.42.91-py3-none-any.whl", hash = "sha256:7a28c3cc6bfab5724ad18899d52402b776a0de7d87fa20c3c5270bcaaf199ce8", size = 14897344, upload-time = "2026-04-17T19:30:44.245Z" }, ] [[package]] @@ -710,11 +710,11 @@ wheels = [ [[package]] name = "cachetools" -version = "7.0.5" +version = "6.2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367, upload-time = "2026-03-09T20:51:29.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918, upload-time = "2026-03-09T20:51:27.33Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" }, ] [[package]] @@ -1577,7 +1577,7 @@ requires-dist = [ { name = "aliyun-log-python-sdk", specifier = ">=0.9.44,<1.0.0" }, { name = "azure-identity", specifier = ">=1.25.3,<2.0.0" }, { name = "bleach", specifier = ">=6.3.0" }, - { name = "boto3", specifier = ">=1.42.88" }, + { name = "boto3", specifier = ">=1.42.91" }, { name = "celery", specifier = ">=5.6.3" }, { name = "croniter", specifier = ">=6.2.2" }, { name = "fastopenapi", extras = ["flask"], specifier = "~=0.7.0" }, @@ -1681,13 +1681,13 @@ dev = [ ] storage = [ { name = "azure-storage-blob", specifier = ">=12.28.0" }, - { name = "bce-python-sdk", specifier = ">=0.9.69" }, + { name = "bce-python-sdk", specifier = ">=0.9.70" }, { name = "cos-python-sdk-v5", specifier = ">=1.9.41" }, { name = "esdk-obs-python", specifier = ">=3.22.2" }, { name = "google-cloud-storage", specifier = ">=3.10.1" }, { name = "opendal", specifier = ">=0.46.0" }, { name = "oss2", specifier = ">=2.19.1" }, - { name = "supabase", specifier = ">=2.18.1" }, + { name = "supabase", specifier = ">=2.28.3" }, { name = "tos", specifier = ">=2.9.0" }, ] tools = [ @@ -4808,16 +4808,17 @@ wheels = [ [[package]] name = "postgrest" -version = "1.1.1" +version = "2.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecation" }, { name = "httpx", extra = ["http2"] }, { name = "pydantic" }, + { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/3e/1b50568e1f5db0bdced4a82c7887e37326585faef7ca43ead86849cb4861/postgrest-1.1.1.tar.gz", hash = "sha256:f3bb3e8c4602775c75c844a31f565f5f3dd584df4d36d683f0b67d01a86be322", size = 15431, upload-time = "2025-06-23T19:21:34.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/60/9378ddd6e21b6005b34aeb42dc7a9ed9985c673c97c9b6a1858f9c52ebbd/postgrest-2.28.3.tar.gz", hash = "sha256:56336e9304950a78315ec7d6c8eb307cdb964d0878a7bec6111392ddb6c16a45", size = 13758, upload-time = "2026-03-20T14:38:06.542Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/71/188a50ea64c17f73ff4df5196ec1553a8f1723421eb2d1069c73bab47d78/postgrest-1.1.1-py3-none-any.whl", hash = "sha256:98a6035ee1d14288484bfe36235942c5fb2d26af6d8120dfe3efbe007859251a", size = 22366, upload-time = "2025-06-23T19:21:33.637Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/6eeb1d53d010d80e800204c1eee6b3d5419a6a2b985c364f56f36cf48cca/postgrest-2.28.3-py3-none-any.whl", hash = "sha256:5a44d6c6d509abdbe0f928c86f0dc31ef26bda36e0357129836ec54dfb50b083", size = 21865, upload-time = "2026-03-20T14:38:05.55Z" }, ] [[package]] @@ -5158,6 +5159,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pyiceberg" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "click" }, + { name = "fsspec" }, + { name = "mmh3" }, + { name = "pydantic" }, + { name = "pyparsing" }, + { name = "pyroaring" }, + { name = "requests" }, + { name = "rich" }, + { name = "strictyaml" }, + { name = "tenacity" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/f0/7616676603fdbd05ab97816337a9b31be08a5f9e1ffd636260812b217e0f/pyiceberg-0.11.1.tar.gz", hash = "sha256:366fe0d5a74e3cf1d4e7cbf3c49e308da60e7835ea268667be9185388f05d7a5", size = 1076075, upload-time = "2026-03-03T00:10:27.61Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/84/a140466b7e0841207e6b77042e03d4ab3a4f9d47e00f0bbbcc5420792bbb/pyiceberg-0.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd423b8ee2f75fc9db09158875abe5e2c952a26ae5e521c3265ab2f9d3511ddf", size = 532981, upload-time = "2026-03-03T00:10:08.906Z" }, + { url = "https://files.pythonhosted.org/packages/17/10/6bedd784010f707680ffd0606d4d11394cf915f4f9f54ae16e8007e00ad4/pyiceberg-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e273242cdca56029af694d7ce18075d47a74d034326d663ff6dd2655a6f44825", size = 533188, upload-time = "2026-03-03T00:10:10.086Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a3/79db617c3cffc963efa8a332707079d3f22fd58067b31a208d358dd89b39/pyiceberg-0.11.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b347d3cc8510f8fbe191956fcda7da372ebb3302789acefca08e352345959003", size = 729546, upload-time = "2026-03-03T00:10:11.413Z" }, + { url = "https://files.pythonhosted.org/packages/06/64/acc11d230c33817bced80d9d947bb49e7bb3a429d76d906523e3df86faf8/pyiceberg-0.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba3a35b4648694783aeae5b77c235a57191c8b1b375c8602b03ae56a6cf4fe7", size = 730263, upload-time = "2026-03-03T00:10:13.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1a/fb067d5150c7309fbf5dd126c648a6afed6259e7bc924ba3c65d0f87a333/pyiceberg-0.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0f958cbca18d05846e3081dfff8575e73d45595441d659847479656dc76f91d", size = 724064, upload-time = "2026-03-03T00:10:14.55Z" }, + { url = "https://files.pythonhosted.org/packages/c1/71/103fdba5b144d55f3bb07347893737cc1d8fd71308108a77b7817c92c544/pyiceberg-0.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c62636a1e9d8a1fc74ffb70383939b9cd93f2c9ee8e12015a50dd75c98a989e", size = 727239, upload-time = "2026-03-03T00:10:16.204Z" }, + { url = "https://files.pythonhosted.org/packages/18/c3/4db64429304c58c039f8e842cd37a9a1c472f596c2868ed2a5d2907b17ed/pyiceberg-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d6b6f0c1e7dd8357f1ba56524bfc870d04ad3c00979db291784a7145497ad3b", size = 531309, upload-time = "2026-03-03T00:10:17.561Z" }, +] + [[package]] name = "pyjwt" version = "2.12.1" @@ -5342,6 +5372,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/32/38ac5af84d96167412024abf5e2f49f15b777987a1942e7a442e8e5fef82/pyrefly-0.61.1-py3-none-win_arm64.whl", hash = "sha256:cef5631e2ab09702274ec2eaaafee28a114891cf85f2d31568b329727e3ff735", size = 12302467, upload-time = "2026-04-17T18:47:31.409Z" }, ] +[[package]] +name = "pyroaring" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/71/134bcaf93d8734051ecbc164dabe695645849249e0fb24209b2dd88e8147/pyroaring-1.0.4.tar.gz", hash = "sha256:99d4217bdfeedc91b82efcec940175a9f9a9137c6476faf7ce5d9c9dd889c8e6", size = 189155, upload-time = "2026-03-19T13:57:27.932Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/a7/9f4977405d3a3fa02cf575951f03a9cda4c01efbe27a19230addee06acc2/pyroaring-1.0.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:924ee997ff1a0f2a184e39e153e9f77e0b928fe908d0aef63d03204c3ed90586", size = 322060, upload-time = "2026-03-19T13:56:04.94Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e9/32fd7125aea82a3d1d29b755edbc7bb531907638c68a5bcc767d20c2be4a/pyroaring-1.0.4-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:b7a527279cc378e893a543a2271d71321b57d21733915a5a14e587532e29265a", size = 685716, upload-time = "2026-03-19T13:56:06.431Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/6b78bd9d743c053fb3b0485a6b1f487e6a658123f06013bee55610b02120/pyroaring-1.0.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:d73979e1a3a6de2b7039dd9a545afa23f3b33f8b6f90a825390a34253d097f96", size = 363373, upload-time = "2026-03-19T13:56:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ff/188c16cdd75d841e50d2779ff7b5d1c5c915a6f23006ef3ab3680f48faca/pyroaring-1.0.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dab3d8577b143c64c1c1659b4d1c69d7fec5baa0d0c0181cdddf6b84f43af00a", size = 1914865, upload-time = "2026-03-19T13:56:09.22Z" }, + { url = "https://files.pythonhosted.org/packages/c8/12/ae7b4fa3682190597cbfb252be570358c5bc55f46f6b05db3fde66dfacc1/pyroaring-1.0.4-cp312-cp312-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dcc7d0133f46163b5390dd151e3305bd47289f7710c6e5444f38453d55b15d1c", size = 1742423, upload-time = "2026-03-19T13:56:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/f7/25/966ea0a9d857ac3f2af1eaebdbfd56e627507b890f8ff7752c32e8e57b57/pyroaring-1.0.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a8b8448c2f7af3b40f17dde23b6739f3b19cf8b24db6f817c549c870d50aae3", size = 2130698, upload-time = "2026-03-19T13:56:12.039Z" }, + { url = "https://files.pythonhosted.org/packages/7a/31/0d0320925cf8bc1fcb53db182359bd673bf6b434f31c6cc69e4f5312c55f/pyroaring-1.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:13ccc488cfe6a227945586090397f299fd40c1a0dc1ed25e8e58a4d068c1ed46", size = 2822746, upload-time = "2026-03-19T13:56:13.274Z" }, + { url = "https://files.pythonhosted.org/packages/b9/98/a5f6d619098e307baf71b14a4df955914e8092f459d19daf80fbbd651fa1/pyroaring-1.0.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dd89ebb7496325fb1b3dbe290dad35bc4da8722b5e3afd2a71b1d6e8ad981725", size = 2657370, upload-time = "2026-03-19T13:56:14.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/13/4d8beb31e4f648326b9b39c8f056fc1cb41422ef4e2be17cb15432c7fd40/pyroaring-1.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06b15bc8e0c272c3bee0c8adc29130178cd792ad99af64d7d7a1eb3069a1e0b8", size = 3088618, upload-time = "2026-03-19T13:56:16.625Z" }, + { url = "https://files.pythonhosted.org/packages/ad/16/95e223db5e60a6def8abcc2a8ebc4c71df23148a602310973c9a6964e3c5/pyroaring-1.0.4-cp312-cp312-win32.whl", hash = "sha256:4dca094f1d0e18901fa3f6b8866fa14a0f9640b22f41f5fc278c20e15e70efee", size = 202455, upload-time = "2026-03-19T13:56:18.124Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2c/5e41a91822c3bbc735382939d354e2c10bae453b18fe5a133f7fdbb33ce9/pyroaring-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:63ab0dcb24933fc9d4ca8c9fa0440f7e177183975990f756a42cddad22fd66db", size = 257479, upload-time = "2026-03-19T13:56:19.25Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d7/7bc58e807e7d6739f4f5c45964a35927e1ff7f591e3943372097d29a00a7/pyroaring-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:226079645dd4098d3619ae5fc19bf9abfd2187a74aba94a8768443e637d406fa", size = 215516, upload-time = "2026-03-19T13:56:20.286Z" }, +] + [[package]] name = "pytest" version = "9.0.3" @@ -5671,16 +5721,16 @@ wheels = [ [[package]] name = "realtime" -version = "2.7.0" +version = "2.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/ca/e408fbdb6b344bf529c7e8bf020372d21114fe538392c72089462edd26e5/realtime-2.7.0.tar.gz", hash = "sha256:6b9434eeba8d756c8faf94fc0a32081d09f250d14d82b90341170602adbb019f", size = 18860, upload-time = "2025-07-28T18:54:22.949Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/3d/ef6ed9221f98766f3a503e6e3ac68fa7ca25c117b383f1efc448294232ac/realtime-2.28.3.tar.gz", hash = "sha256:5cc83a6217874426799d8bf74e96d904ac6fa77c39fa8982fa99287947eb2cbf", size = 18723, upload-time = "2026-03-20T14:38:08.424Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/07/a5c7aef12f9a3497f5ad77157a37915645861e8b23b89b2ad4b0f11b48ad/realtime-2.7.0-py3-none-any.whl", hash = "sha256:d55a278803529a69d61c7174f16563a9cfa5bacc1664f656959694481903d99c", size = 22409, upload-time = "2025-07-28T18:54:21.383Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d5/659405f9d4c9b022b7ac02bd52986ccc081f211db081051440f46bf4f358/realtime-2.28.3-py3-none-any.whl", hash = "sha256:efe484d6d39024c7e00ef70f70be600142e9407e5d802de8c96e86e014ce3b36", size = 22378, upload-time = "2026-03-20T14:38:07.144Z" }, ] [[package]] @@ -6162,16 +6212,18 @@ wheels = [ [[package]] name = "storage3" -version = "0.12.1" +version = "2.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecation" }, { name = "httpx", extra = ["http2"] }, - { name = "python-dateutil" }, + { name = "pydantic" }, + { name = "pyiceberg" }, + { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/e2/280fe75f65e7a3ca680b7843acfc572a63aa41230e3d3c54c66568809c85/storage3-0.12.1.tar.gz", hash = "sha256:32ea8f5eb2f7185c2114a4f6ae66d577722e32503f0a30b56e7ed5c7f13e6b48", size = 10198, upload-time = "2025-08-05T18:09:11.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/b5/18df59ba92951d74774eb0265072bf236ead5e3cbc4b802d8bf1cf3581a0/storage3-2.28.3.tar.gz", hash = "sha256:2b3f843cbd44c4a3b483ec076a12c27de88c0ad5358a43067ed44ef08292353f", size = 20109, upload-time = "2026-03-20T14:38:11.467Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3b/c5f8709fc5349928e591fee47592eeff78d29a7d75b097f96a4e01de028d/storage3-0.12.1-py3-none-any.whl", hash = "sha256:9da77fd4f406b019fdcba201e9916aefbf615ef87f551253ce427d8136459a34", size = 18420, upload-time = "2025-08-05T18:09:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a5/2dbe216954e026a8c2e2dc7dfa5fd7b1a1ae0824d10972e62462f4f15aca/storage3-2.28.3-py3-none-any.whl", hash = "sha256:bac35c5087619174448fdef6a337db4e3dfebf3de69f685bd706de93ddcdad69", size = 28239, upload-time = "2026-03-20T14:38:10.423Z" }, ] [[package]] @@ -6183,9 +6235,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851, upload-time = "2023-06-29T22:02:56.947Z" }, ] +[[package]] +name = "strictyaml" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/08/efd28d49162ce89c2ad61a88bd80e11fb77bc9f6c145402589112d38f8af/strictyaml-1.7.3.tar.gz", hash = "sha256:22f854a5fcab42b5ddba8030a0e4be51ca89af0267961c8d6cfa86395586c407", size = 115206, upload-time = "2023-03-10T12:50:27.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/7c/a81ef5ef10978dd073a854e0fa93b5d8021d0594b639cc8f6453c3c78a1d/strictyaml-1.7.3-py3-none-any.whl", hash = "sha256:fb5c8a4edb43bebb765959e420f9b3978d7f1af88c80606c03fb420888f5d1c7", size = 123917, upload-time = "2023-03-10T12:50:17.242Z" }, +] + [[package]] name = "supabase" -version = "2.18.1" +version = "2.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -6194,37 +6258,39 @@ dependencies = [ { name = "storage3" }, { name = "supabase-auth" }, { name = "supabase-functions" }, + { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/d2/3b135af55dd5788bd47875bb81f99c870054b990c030e51fd641a61b10b5/supabase-2.18.1.tar.gz", hash = "sha256:205787b1fbb43d6bc997c06fe3a56137336d885a1b56ec10f0012f2a2905285d", size = 11549, upload-time = "2025-08-12T19:02:27.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/98/2f1c95a2269ce995a34f275760b1c2ee71ee7a75649238ca0470afdfc2ef/supabase-2.28.3.tar.gz", hash = "sha256:1200961e46cdec17c7c280a1e09a159544643eada2759591ea69835303a2e1a4", size = 9687, upload-time = "2026-03-20T14:38:13.272Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/33/0e0062fea22cfe01d466dee83f56b3ed40c89bdcbca671bafeba3fe86b92/supabase-2.18.1-py3-none-any.whl", hash = "sha256:4fdd7b7247178a847f97ecd34f018dcb4775e487c8ff46b1208a01c933691fe9", size = 18683, upload-time = "2025-08-12T19:02:26.68Z" }, + { url = "https://files.pythonhosted.org/packages/de/96/1b48eb664153401c22087bbf77f6a428965e830cc8e0d0c6d68324a28342/supabase-2.28.3-py3-none-any.whl", hash = "sha256:52a7ce4a1d2d55fa6d657bf4760672935058143a5bedc64165851be25ce01dbd", size = 16634, upload-time = "2026-03-20T14:38:12.319Z" }, ] [[package]] name = "supabase-auth" -version = "2.12.3" +version = "2.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx", extra = ["http2"] }, { name = "pydantic" }, - { name = "pyjwt" }, + { name = "pyjwt", extra = ["crypto"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e9/3d6f696a604752803b9e389b04d454f4b26a29b5d155b257fea4af8dc543/supabase_auth-2.12.3.tar.gz", hash = "sha256:8d3b67543f3b27f5adbfe46b66990424c8504c6b08c1141ec572a9802761edc2", size = 38430, upload-time = "2025-07-04T06:49:22.906Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/6f/1bf81293374ba71183b321bf5dfd7151c3db0c2e24715f35783bc1c56385/supabase_auth-2.28.3.tar.gz", hash = "sha256:41c049da82f9d7fc2f111808e57e984015f128d033f58caa67fd76f428472807", size = 39160, upload-time = "2026-03-20T14:38:15.128Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/a6/4102d5fa08a8521d9432b4d10bb58fedbd1f92b211d1b45d5394f5cb9021/supabase_auth-2.12.3-py3-none-any.whl", hash = "sha256:15c7580e1313d30ffddeb3221cb3cdb87c2a80fd220bf85d67db19cd1668435b", size = 44417, upload-time = "2025-07-04T06:49:21.351Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d3/e012315aa895b434fa77bc475e2dfeb87119e67918ecca4d88a25f96814d/supabase_auth-2.28.3-py3-none-any.whl", hash = "sha256:e47c5caec7bbf3c258964d027fbbe99f3cc4a956d3a635f898c962b4d22832dd", size = 48378, upload-time = "2026-03-20T14:38:14.169Z" }, ] [[package]] name = "supabase-functions" -version = "0.10.1" +version = "2.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx", extra = ["http2"] }, { name = "strenum" }, + { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/e4/6df7cd4366396553449e9907c745862ebf010305835b2bac99933dd7db9d/supabase_functions-0.10.1.tar.gz", hash = "sha256:4779d33a1cc3d4aea567f586b16d8efdb7cddcd6b40ce367c5fb24288af3a4f1", size = 5025, upload-time = "2025-06-23T18:26:12.239Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ea/59bf327960e5384fcc9e69afbdf97260a2cf2684a25c0731968a8a393b9c/supabase_functions-2.28.3.tar.gz", hash = "sha256:5a6255d60a263d44251c5ca250fcdde2408a8483a8bf31f4ac80255de8f3fcae", size = 4679, upload-time = "2026-03-20T14:38:16.742Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/06/060118a1e602c9bda8e4bf950bd1c8b5e1542349f2940ec57541266fabe1/supabase_functions-0.10.1-py3-none-any.whl", hash = "sha256:1db85e20210b465075aacee4e171332424f7305f9903c5918096be1423d6fcc5", size = 8275, upload-time = "2025-06-23T18:26:10.387Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ca/1e720f1347a88519e3d52b6d801cd031c3a7a5df66640c5dc6e81d925057/supabase_functions-2.28.3-py3-none-any.whl", hash = "sha256:eb30578866103fed9322c54e95dd68c2f1a4b6b177e129d9369edd364637904e", size = 8801, upload-time = "2026-03-20T14:38:15.883Z" }, ] [[package]] From 5cae61eb5aad4381b4b6dfc49e41d70a77bec978 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 20 Apr 2026 18:25:40 +0800 Subject: [PATCH 012/253] ci: [codex] Remove anti-slop GitHub Actions workflow (#35432) --- .github/workflows/anti-slop.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/workflows/anti-slop.yml diff --git a/.github/workflows/anti-slop.yml b/.github/workflows/anti-slop.yml deleted file mode 100644 index b0f0a36bc9..0000000000 --- a/.github/workflows/anti-slop.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Anti-Slop PR Check - -on: - pull_request_target: - types: [opened, edited, synchronize] - -permissions: - pull-requests: write - contents: read - -jobs: - anti-slop: - runs-on: ubuntu-latest - steps: - - uses: peakoss/anti-slop@85daca1880e9e1af197fc06ea03349daf08f4202 # v0.2.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - close-pr: false - failure-add-pr-labels: "needs-revision" From df389eba1cd2e697ef1a93ae44816f8a9bcb09ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:58:50 +0000 Subject: [PATCH 013/253] chore(deps): bump the github-actions-dependencies group across 1 directory with 7 updates (#35435) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/api-tests.yml | 8 ++++---- .github/workflows/autofix.yml | 10 +++++----- .github/workflows/db-migration-test.yml | 8 ++++---- .github/workflows/pyrefly-diff.yml | 2 +- .github/workflows/pyrefly-type-coverage-comment.yml | 2 +- .github/workflows/pyrefly-type-coverage.yml | 2 +- .github/workflows/style.yml | 12 ++++++------ .github/workflows/tool-test-sdks.yaml | 2 +- .github/workflows/translate-i18n-claude.yml | 2 +- .github/workflows/vdb-tests-full.yml | 4 ++-- .github/workflows/vdb-tests.yml | 4 ++-- .github/workflows/web-e2e.yml | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index fd910531db..717413937f 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -35,7 +35,7 @@ jobs: persist-credentials: false - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -84,7 +84,7 @@ jobs: persist-credentials: false - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -105,7 +105,7 @@ jobs: run: sh .github/workflows/expose_service_ports.sh - name: Set up Sandbox - uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0 + uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0 with: compose-file: | docker/docker-compose.middleware.yaml @@ -156,7 +156,7 @@ jobs: persist-credentials: false - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: "3.12" diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 3946834e09..35683b112f 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -25,7 +25,7 @@ jobs: - name: Check Docker Compose inputs if: github.event_name != 'merge_group' id: docker-compose-changes - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | docker/generate_docker_compose @@ -35,7 +35,7 @@ jobs: - name: Check web inputs if: github.event_name != 'merge_group' id: web-changes - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | web/** @@ -48,7 +48,7 @@ jobs: - name: Check api inputs if: github.event_name != 'merge_group' id: api-changes - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | api/** @@ -58,7 +58,7 @@ jobs: python-version: "3.11" - if: github.event_name != 'merge_group' - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - name: Generate Docker Compose if: github.event_name != 'merge_group' && steps.docker-compose-changes.outputs.any_changed == 'true' @@ -123,4 +123,4 @@ jobs: vp exec eslint --concurrency=2 --prune-suppressions --quiet || true - if: github.event_name != 'merge_group' - uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 # v1.3.3 + uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a # v1.3.4 diff --git a/.github/workflows/db-migration-test.yml b/.github/workflows/db-migration-test.yml index 5991abe3ba..17b867dd6d 100644 --- a/.github/workflows/db-migration-test.yml +++ b/.github/workflows/db-migration-test.yml @@ -19,7 +19,7 @@ jobs: persist-credentials: false - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: "3.12" @@ -40,7 +40,7 @@ jobs: cp middleware.env.example middleware.env - name: Set up Middlewares - uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0 + uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0 with: compose-file: | docker/docker-compose.middleware.yaml @@ -69,7 +69,7 @@ jobs: persist-credentials: false - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: "3.12" @@ -94,7 +94,7 @@ jobs: sed -i 's/DB_USERNAME=postgres/DB_USERNAME=mysql/' middleware.env - name: Set up Middlewares - uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0 + uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0 with: compose-file: | docker/docker-compose.middleware.yaml diff --git a/.github/workflows/pyrefly-diff.yml b/.github/workflows/pyrefly-diff.yml index ac3732579c..eb15cd6f75 100644 --- a/.github/workflows/pyrefly-diff.yml +++ b/.github/workflows/pyrefly-diff.yml @@ -22,7 +22,7 @@ jobs: fetch-depth: 0 - name: Setup Python & UV - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true diff --git a/.github/workflows/pyrefly-type-coverage-comment.yml b/.github/workflows/pyrefly-type-coverage-comment.yml index 974da99aad..3c6c96a664 100644 --- a/.github/workflows/pyrefly-type-coverage-comment.yml +++ b/.github/workflows/pyrefly-type-coverage-comment.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Python & UV - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true diff --git a/.github/workflows/pyrefly-type-coverage.yml b/.github/workflows/pyrefly-type-coverage.yml index c795c32e31..0599c94eef 100644 --- a/.github/workflows/pyrefly-type-coverage.yml +++ b/.github/workflows/pyrefly-type-coverage.yml @@ -22,7 +22,7 @@ jobs: fetch-depth: 0 - name: Setup Python & UV - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 29f5b090f8..d8c7ebbad3 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -25,7 +25,7 @@ jobs: - name: Check changed files id: changed-files - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | api/** @@ -33,7 +33,7 @@ jobs: - name: Setup UV and Python if: steps.changed-files.outputs.any_changed == 'true' - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: false python-version: "3.12" @@ -73,7 +73,7 @@ jobs: - name: Check changed files id: changed-files - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | web/** @@ -95,7 +95,7 @@ jobs: - name: Restore ESLint cache if: steps.changed-files.outputs.any_changed == 'true' id: eslint-cache-restore - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: .eslintcache key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }} @@ -124,7 +124,7 @@ jobs: - name: Save ESLint cache if: steps.changed-files.outputs.any_changed == 'true' && success() && steps.eslint-cache-restore.outputs.cache-hit != 'true' - uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: .eslintcache key: ${{ steps.eslint-cache-restore.outputs.cache-primary-key }} @@ -142,7 +142,7 @@ jobs: - name: Check changed files id: changed-files - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | **.sh diff --git a/.github/workflows/tool-test-sdks.yaml b/.github/workflows/tool-test-sdks.yaml index 467f31fccf..bf33207a14 100644 --- a/.github/workflows/tool-test-sdks.yaml +++ b/.github/workflows/tool-test-sdks.yaml @@ -30,7 +30,7 @@ jobs: persist-credentials: false - name: Use Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 cache: '' diff --git a/.github/workflows/translate-i18n-claude.yml b/.github/workflows/translate-i18n-claude.yml index 541200293d..eecbbb1a56 100644 --- a/.github/workflows/translate-i18n-claude.yml +++ b/.github/workflows/translate-i18n-claude.yml @@ -158,7 +158,7 @@ jobs: - name: Run Claude Code for Translation Sync if: steps.context.outputs.CHANGED_FILES != '' - uses: anthropics/claude-code-action@b47fd721da662d48c5680e154ad16a73ed74d2e0 # v1.0.93 + uses: anthropics/claude-code-action@38ec876110f9fbf8b950c79f534430740c3ac009 # v1.0.101 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/vdb-tests-full.yml b/.github/workflows/vdb-tests-full.yml index f0def8fe7a..b79e8927d7 100644 --- a/.github/workflows/vdb-tests-full.yml +++ b/.github/workflows/vdb-tests-full.yml @@ -36,7 +36,7 @@ jobs: remove_tool_cache: true - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -65,7 +65,7 @@ jobs: # tiflash - name: Set up Full Vector Store Matrix - uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0 + uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0 with: compose-file: | docker/docker-compose.yaml diff --git a/.github/workflows/vdb-tests.yml b/.github/workflows/vdb-tests.yml index f3966f15b9..bd13d662c3 100644 --- a/.github/workflows/vdb-tests.yml +++ b/.github/workflows/vdb-tests.yml @@ -33,7 +33,7 @@ jobs: remove_tool_cache: true - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -62,7 +62,7 @@ jobs: # tiflash - name: Set up Vector Stores for Smoke Coverage - uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0 + uses: hoverkraft-tech/compose-action@d2bee4f07e8ca410d6b196d00f90c12e7d48c33a # v2.6.0 with: compose-file: | docker/docker-compose.yaml diff --git a/.github/workflows/web-e2e.yml b/.github/workflows/web-e2e.yml index 10dc31bde8..6bd4d4f406 100644 --- a/.github/workflows/web-e2e.yml +++ b/.github/workflows/web-e2e.yml @@ -28,7 +28,7 @@ jobs: uses: ./.github/actions/setup-web - name: Setup UV and Python - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true python-version: "3.12" From a633387e9bc247c6c91e625746b080245a0610b1 Mon Sep 17 00:00:00 2001 From: sicnuyudidi Date: Mon, 20 Apr 2026 21:43:17 +0800 Subject: [PATCH 014/253] chore: fix oxlint warnings (unused variables and imports) (#35249) Co-authored-by: Your Name Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Asuka Minato --- .../config-modal/__tests__/index-logic.spec.tsx | 2 +- web/app/components/base/markdown-with-directive/index.tsx | 2 +- web/app/components/base/markdown/streamdown-wrapper.tsx | 2 +- .../documents/detail/metadata/hooks/use-metadata-state.ts | 2 +- .../marketplace/search-box/__tests__/tags-filter.spec.tsx | 2 +- .../subscription-list/__tests__/log-viewer.spec.tsx | 2 +- .../components/__tests__/reasoning-config-form.spec.tsx | 2 +- .../__tests__/strategy-picker.spec.tsx | 2 +- .../auto-update-setting/__tests__/tool-picker.spec.tsx | 2 +- .../workflow-app/hooks/__tests__/use-workflow-run.spec.ts | 3 ++- .../components/workflow/hooks/use-nodes-interactions.ts | 4 ++-- .../nodes/assigner/__tests__/integration.spec.tsx | 6 +++--- .../nodes/http/components/__tests__/curl-panel.spec.tsx | 2 +- .../workflow/nodes/loop/__tests__/integration.spec.tsx | 4 ++-- web/context/modal-context-provider.tsx | 2 +- web/public/embed.js | 8 +++++--- web/scripts/check-i18n.js | 4 ++-- web/scripts/component-analyzer.js | 8 ++++---- web/types/doc-paths.ts | 3 --- 19 files changed, 31 insertions(+), 31 deletions(-) diff --git a/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx b/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx index 77f2b54533..e6cb56f490 100644 --- a/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx @@ -43,7 +43,7 @@ vi.mock('../form-fields', () => ({ > invalid-name-change - + diff --git a/web/app/components/base/markdown-with-directive/index.tsx b/web/app/components/base/markdown-with-directive/index.tsx index 552d8a3aaa..8da4cf7ede 100644 --- a/web/app/components/base/markdown-with-directive/index.tsx +++ b/web/app/components/base/markdown-with-directive/index.tsx @@ -91,7 +91,7 @@ function buildDirectiveRehypePlugins(): PluggableList { ]) const attributes: Record = { - ...(defaultSanitizeSchema.attributes ?? {}), + ...defaultSanitizeSchema.attributes, } for (const [tagName, allowedAttributes] of Object.entries(DIRECTIVE_ALLOWED_TAGS)) diff --git a/web/app/components/base/markdown/streamdown-wrapper.tsx b/web/app/components/base/markdown/streamdown-wrapper.tsx index 342f5cf44b..e566ffc611 100644 --- a/web/app/components/base/markdown/streamdown-wrapper.tsx +++ b/web/app/components/base/markdown/streamdown-wrapper.tsx @@ -86,7 +86,7 @@ function buildRehypePlugins(extraPlugins?: PluggableList): PluggableList { ]) const mergedAttributes: Record = { - ...(defaultSanitizeSchema.attributes ?? {}), + ...defaultSanitizeSchema.attributes, } for (const tag of Object.keys(ALLOWED_TAGS)) { diff --git a/web/app/components/datasets/documents/detail/metadata/hooks/use-metadata-state.ts b/web/app/components/datasets/documents/detail/metadata/hooks/use-metadata-state.ts index 7bfa64ac8e..7fc0c17cb0 100644 --- a/web/app/components/datasets/documents/detail/metadata/hooks/use-metadata-state.ts +++ b/web/app/components/datasets/documents/detail/metadata/hooks/use-metadata-state.ts @@ -75,7 +75,7 @@ export function useMetadataState({ docDetail, onUpdate }: UseMetadataStateOption setEditStatus(true) } const cancelEdit = () => { - setMetadataParams({ documentType: docType || '', metadata: { ...(docDetail?.doc_metadata || {}) } }) + setMetadataParams({ documentType: docType || '', metadata: { ...docDetail?.doc_metadata } }) setEditStatus(!docType) if (!docType) setShowDocTypes(true) diff --git a/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx b/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx index bb5d8e734c..117b8cdfab 100644 --- a/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx +++ b/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx @@ -47,7 +47,7 @@ vi.mock('@/app/components/base/input', () => ({ })) vi.mock('@/app/components/base/portal-to-follow-elem', async () => { - const React = await import('react') + const _React = await import('react') return { PortalToFollowElem: ({ children }: { children: React.ReactNode }) =>
{children}
, PortalToFollowElemTrigger: ({ diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx index 3594c10ce2..3b179d0881 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx @@ -145,7 +145,7 @@ describe('LogViewer', () => { }) it('should parse request data when it is raw JSON', () => { - const log = createLog({ request: { ...createLog().request, data: '{\"hello\":1}' } }) + const log = createLog({ request: { ...createLog().request, data: '{"hello":1}' } }) render() diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/reasoning-config-form.spec.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/reasoning-config-form.spec.tsx index beab35595c..3c56089b97 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/reasoning-config-form.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/reasoning-config-form.spec.tsx @@ -67,7 +67,7 @@ vi.mock('@/app/components/plugins/plugin-detail-panel/model-selector', () => ({ vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', () => ({ default: ({ onChange }: { onChange: (value: string) => void }) => ( - ), diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/strategy-picker.spec.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/strategy-picker.spec.tsx index 5be10ff146..e287e48985 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/strategy-picker.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/strategy-picker.spec.tsx @@ -14,7 +14,7 @@ vi.mock('@langgenius/dify-ui/button', () => ({ })) vi.mock('@/app/components/base/portal-to-follow-elem', async () => { - const React = await import('react') + const _React = await import('react') return { PortalToFollowElem: ({ open, diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx index 9e63622d3f..e89b1b3161 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx @@ -22,7 +22,7 @@ vi.mock('@/app/components/base/loading', () => ({ })) vi.mock('@/app/components/base/portal-to-follow-elem', async () => { - const React = await import('react') + const _React = await import('react') return { PortalToFollowElem: ({ open, diff --git a/web/app/components/workflow-app/hooks/__tests__/use-workflow-run.spec.ts b/web/app/components/workflow-app/hooks/__tests__/use-workflow-run.spec.ts index 7b54598774..59ba667f8d 100644 --- a/web/app/components/workflow-app/hooks/__tests__/use-workflow-run.spec.ts +++ b/web/app/components/workflow-app/hooks/__tests__/use-workflow-run.spec.ts @@ -501,7 +501,8 @@ describe('useWorkflowRun', () => { windowWithDebugControllers.__allTriggersDebugAbortController = { abort: allTriggersAbort } const refController = new AbortController() const refAbortSpy = vi.spyOn(refController, 'abort') - const { getAbortController } = mocks.mockSsePost.mock.calls.at(-1)?.[2] as { + const lastCall = mocks.mockSsePost.mock.calls.at(-1) + const { getAbortController } = (lastCall?.[2] ?? {}) as { getAbortController?: (controller: AbortController) => void } getAbortController?.(refController) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index f0e23586eb..0ce86602fa 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -1543,7 +1543,7 @@ export const useNodesInteractions = () => { targetHandle, type: CUSTOM_EDGE, data: { - ...(edge.data || {}), + ...edge.data, sourceType: newCurrentNode.data.type, targetType: targetNodeForEdge.data.type, isInIteration, @@ -1583,7 +1583,7 @@ export const useNodesInteractions = () => { targetHandle, type: CUSTOM_EDGE, data: { - ...(edge.data || {}), + ...edge.data, sourceType: sourceNode.data.type, targetType: newCurrentNode.data.type, isInIteration: newNodeIsInIteration, diff --git a/web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx b/web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx index 5cd378ffa2..f1feb0a669 100644 --- a/web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx +++ b/web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx @@ -320,7 +320,7 @@ describe('assigner path', () => { true)} filterToAssignedVar={vi.fn(() => true)} @@ -332,9 +332,9 @@ describe('assigner path', () => { />, ) - fireEvent.change(screen.getByLabelText('code-editor'), { target: { value: '{\"a\":2}' } }) + fireEvent.change(screen.getByLabelText('code-editor'), { target: { value: '{"a":2}' } }) expect(onChange).toHaveBeenLastCalledWith([ - createOperation({ operation: WriteMode.set, value: '{\"a\":2}' }), + createOperation({ operation: WriteMode.set, value: '{"a":2}' }), ], '{"a":2}') onChange.mockClear() diff --git a/web/app/components/workflow/nodes/http/components/__tests__/curl-panel.spec.tsx b/web/app/components/workflow/nodes/http/components/__tests__/curl-panel.spec.tsx index 141f8fd5f9..069ab4547e 100644 --- a/web/app/components/workflow/nodes/http/components/__tests__/curl-panel.spec.tsx +++ b/web/app/components/workflow/nodes/http/components/__tests__/curl-panel.spec.tsx @@ -32,7 +32,7 @@ describe('curl-panel', () => { describe('parseCurl', () => { it('should parse method, headers, json body, and query params from a valid curl command', () => { - const { node, error } = curlParser.parseCurl('curl -X POST -H \"Authorization: Bearer token\" --json \"{\"name\":\"openai\"}\" https://example.com/users?page=1&size=2') + const { node, error } = curlParser.parseCurl('curl -X POST -H "Authorization: Bearer token" --json "{"name":"openai"}" https://example.com/users?page=1&size=2') expect(error).toBeNull() expect(node).toMatchObject({ diff --git a/web/app/components/workflow/nodes/loop/__tests__/integration.spec.tsx b/web/app/components/workflow/nodes/loop/__tests__/integration.spec.tsx index 0af05e9674..a4a00fee67 100644 --- a/web/app/components/workflow/nodes/loop/__tests__/integration.spec.tsx +++ b/web/app/components/workflow/nodes/loop/__tests__/integration.spec.tsx @@ -563,7 +563,7 @@ describe('loop path', () => { id: 'loop-var-object', var_type: VarType.arrayObject, value_type: ValueType.constant, - value: '[{\"id\":1}]', + value: '[{"id":1}]', })} onChange={onObjectChange} /> @@ -571,7 +571,7 @@ describe('loop path', () => { ) fireEvent.change(screen.getByDisplayValue('draft'), { target: { value: 'published' } }) - fireEvent.change(screen.getByLabelText('code-editor'), { target: { value: '[{\"id\":2}]' } }) + fireEvent.change(screen.getByLabelText('code-editor'), { target: { value: '[{"id":2}]' } }) expect(onStringChange).toHaveBeenCalledWith('published') expect(onObjectChange).toHaveBeenCalledWith('[{"id":2}]') diff --git a/web/context/modal-context-provider.tsx b/web/context/modal-context-provider.tsx index 5facc9a202..fcc37a1030 100644 --- a/web/context/modal-context-provider.tsx +++ b/web/context/modal-context-provider.tsx @@ -122,7 +122,7 @@ export const ModalContextProvider = ({ const setShowAccountSettingModal = useCallback((next: SetStateAction | null>) => { const currentState = accountSettingTab - ? { payload: accountSettingTab, ...(accountSettingCallbacksRef.current ?? {}) } + ? { payload: accountSettingTab, ...accountSettingCallbacksRef.current } : null const resolvedState = typeof next === 'function' ? next(currentState) : next if (!resolvedState) { diff --git a/web/public/embed.js b/web/public/embed.js index d5eabc0533..f7c6fdaf4a 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -312,9 +312,11 @@ } targetIframe.style.display = targetIframe.style.display === "none" ? "block" : "none"; - targetIframe.style.display === "none" - ? setSvgIcon("open") - : setSvgIcon("close"); + if (targetIframe.style.display === "none") { + setSvgIcon("open") + } else { + setSvgIcon("close") + } if (targetIframe.style.display === "none") { document.removeEventListener("keydown", handleEscKey); diff --git a/web/scripts/check-i18n.js b/web/scripts/check-i18n.js index 34b842cc00..6936bcb452 100644 --- a/web/scripts/check-i18n.js +++ b/web/scripts/check-i18n.js @@ -116,7 +116,7 @@ async function getKeysFromLanguage(language) { } // Filter only .json files - const translationFiles = files.filter(file => /\.json$/.test(file)) + const translationFiles = files.filter(file => file.endsWith('.json')) translationFiles.forEach((file) => { const filePath = path.join(folderPath, file) @@ -263,7 +263,7 @@ async function main() { // Get all translation files const i18nFolder = path.resolve(__dirname, '../i18n', language) const files = fs.readdirSync(i18nFolder) - .filter(file => /\.json$/.test(file)) + .filter(file => file.endsWith('.json')) .map(file => file.replace(/\.json$/, '')) .filter(f => targetFiles.length === 0 || targetFiles.includes(f)) diff --git a/web/scripts/component-analyzer.js b/web/scripts/component-analyzer.js index 4612981f82..59f1e3f10e 100644 --- a/web/scripts/component-analyzer.js +++ b/web/scripts/component-analyzer.js @@ -206,10 +206,10 @@ export class ComponentAnalyzer { const escapedName = ComponentAnalyzer.escapeRegExp(searchName) const patterns = [ - new RegExp(`from\\s+['\"][^'\"]*(?:/|^)${escapedName}(?:['\"/]|$)`), - new RegExp(`import\\s*\\(\\s*['\"][^'\"]*(?:/|^)${escapedName}(?:['\"/]|$)`), - new RegExp(`export\\s+(?:\\*|{[^}]*})\\s*from\\s+['\"][^'\"]*(?:/|^)${escapedName}(?:['\"/]|$)`), - new RegExp(`require\\(\\s*['\"][^'\"]*(?:/|^)${escapedName}(?:['\"/]|$)`), + new RegExp(`from\\s+['"][^'"]*(?:/|^)${escapedName}(?:['"/]|$)`), + new RegExp(`import\\s*\\(\\s*['"][^'"]*(?:/|^)${escapedName}(?:['"/]|$)`), + new RegExp(`export\\s+(?:\\*|{[^}]*})\\s*from\\s+['"][^'"]*(?:/|^)${escapedName}(?:['"/]|$)`), + new RegExp(`require\\s*\\(\\s*['"][^'"]*(?:/|^)${escapedName}(?:['"/]|$)`), ] const visited = new Set() diff --git a/web/types/doc-paths.ts b/web/types/doc-paths.ts index a0f2d12097..3f030a2733 100644 --- a/web/types/doc-paths.ts +++ b/web/types/doc-paths.ts @@ -272,9 +272,6 @@ export type DocPathWithoutLang = | DocPathWithoutLangBase | `${DocPathWithoutLangBase}#${string}` -// Full documentation path with language prefix -type DifyDocPath = `${DocLanguage}/${DocPathWithoutLang}` - // API Reference path translations (English -> other languages) export const apiReferencePathTranslations: Record = { '/api-reference/annotations/configure-annotation-reply': { zh: '/api-reference/标注管理/配置标注回复', ja: '/api-reference/アノテーション管理/アノテーション返信を設定' }, From da00de668886796503bab9e83c66151f391a0027 Mon Sep 17 00:00:00 2001 From: Markus Lobedann Date: Tue, 21 Apr 2026 03:59:21 +0200 Subject: [PATCH 015/253] fix: increase maximum PostgreSQL connections to 200 (#35439) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- docker/.env.example | 2 +- docker/docker-compose.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 8176155698..ec7d572057 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -273,7 +273,7 @@ SQLALCHEMY_POOL_TIMEOUT=30 # Default is 100 # # Reference: https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-MAX-CONNECTIONS -POSTGRES_MAX_CONNECTIONS=100 +POSTGRES_MAX_CONNECTIONS=200 # Sets the amount of shared memory used for postgres's shared buffers. # Default is 128MB diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index a10fdf77c6..aaf099453a 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -70,7 +70,7 @@ x-shared-env: &shared-api-worker-env SQLALCHEMY_POOL_PRE_PING: ${SQLALCHEMY_POOL_PRE_PING:-false} SQLALCHEMY_POOL_USE_LIFO: ${SQLALCHEMY_POOL_USE_LIFO:-false} SQLALCHEMY_POOL_TIMEOUT: ${SQLALCHEMY_POOL_TIMEOUT:-30} - POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100} + POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-200} POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-128MB} POSTGRES_WORK_MEM: ${POSTGRES_WORK_MEM:-4MB} POSTGRES_MAINTENANCE_WORK_MEM: ${POSTGRES_MAINTENANCE_WORK_MEM:-64MB} From d583b1b835d3817ffb9216a2ea4e84a01ccbfacc Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Tue, 21 Apr 2026 10:44:52 +0800 Subject: [PATCH 016/253] refactor(web): continue replacing PortalToFollowElem with Popover components (#35431) Co-authored-by: CodingOnStar Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 186 ------------------ .../__tests__/base-ui-popover.spec.tsx | 127 ++++++++++++ web/__mocks__/base-ui-popover.tsx | 154 +++++++++++++++ .../overview/tracing/config-button.tsx | 49 +++-- .../inputs-form/view-form-dropdown.tsx | 32 +-- .../chat/citation/__tests__/popup.spec.tsx | 2 + .../base/chat/chat/citation/popup.tsx | 39 ++-- .../inputs-form/view-form-dropdown.tsx | 40 ++-- .../date-picker/__tests__/index.spec.tsx | 19 +- .../date-picker/index.tsx | 87 ++++---- .../time-picker/__tests__/index.spec.tsx | 16 +- .../time-picker/index.tsx | 101 +++++----- .../base/date-and-time-picker/types.ts | 4 +- .../__tests__/setting-modal.spec.tsx | 9 +- .../file-upload/setting-modal.tsx | 43 ++-- .../__tests__/voice-settings.spec.tsx | 78 ++++---- .../text-to-speech/voice-settings.tsx | 43 ++-- .../file-from-link-or-local/index.tsx | 28 +-- .../image-uploader/chat-image-uploader.tsx | 56 +++--- .../text-generation-image-uploader.tsx | 59 +++--- .../base/portal-to-follow-elem/index.tsx | 11 +- .../__tests__/component.spec.tsx | 41 ++++ .../plugins/context-block/component.tsx | 53 ++--- .../__tests__/component.spec.tsx | 43 ++++ .../plugins/history-block/component.tsx | 52 ++--- .../document-picker/__tests__/index.spec.tsx | 78 +++----- .../preview-document-picker.spec.tsx | 118 +++++------ .../datasets/common/document-picker/index.tsx | 68 ++++--- .../preview-document-picker.tsx | 53 ++--- .../steps/__tests__/preview-panel.spec.tsx | 19 +- .../settings/permission-selector/index.tsx | 180 +++++++++-------- .../__tests__/selector.spec.tsx | 2 + .../api-based-extension-page/selector.tsx | 67 ++++--- .../__tests__/configure.spec.tsx | 10 + .../data-source-page-new/configure.tsx | 50 +++-- .../__tests__/add-custom-model.spec.tsx | 38 ++-- .../model-auth/add-custom-model.tsx | 68 ++++--- .../authorized/__tests__/index.spec.tsx | 2 + .../model-auth/authorized/index.tsx | 83 ++++---- .../search-box/__tests__/index.spec.tsx | 25 ++- .../search-box/__tests__/tags-filter.spec.tsx | 50 +++-- .../marketplace/search-box/tags-filter.tsx | 82 ++++---- .../authorized/__tests__/index.spec.tsx | 2 + .../plugins/plugin-auth/authorized/index.tsx | 117 ++++++----- .../__tests__/app-picker.spec.tsx | 12 +- .../app-selector/__tests__/index.spec.tsx | 12 +- .../app-selector/app-picker.tsx | 43 ++-- .../app-selector/index.tsx | 52 +++-- .../tool-selector/__tests__/index.spec.tsx | 12 +- .../tool-selector/index.tsx | 117 +++++------ .../__tests__/category-filter.spec.tsx | 28 +-- .../__tests__/index.spec.tsx | 54 +++-- .../__tests__/tag-filter.spec.tsx | 58 +++--- .../filter-management/category-filter.tsx | 103 +++++----- .../filter-management/tag-filter.tsx | 96 ++++----- .../__tests__/index.spec.tsx | 38 +--- .../publisher/__tests__/index.spec.tsx | 61 +++++- .../publisher/__tests__/popup.spec.tsx | 96 ++++++++- .../rag-pipeline-header/publisher/index.tsx | 49 +++-- .../rag-pipeline-header/publisher/popup.tsx | 14 +- .../block-selector/__tests__/main.spec.tsx | 49 +++++ .../workflow/block-selector/main.tsx | 103 ++++++---- .../header/__tests__/view-history.spec.tsx | 84 ++++---- .../workflow/header/view-history.tsx | 106 +++++----- .../workflow/header/view-workflow-history.tsx | 97 +++++---- .../__tests__/button-style-dropdown.spec.tsx | 49 +---- .../components/button-style-dropdown.tsx | 48 +++-- .../components/metadata/metadata-trigger.tsx | 50 ++--- .../components/retrieval-config.tsx | 59 +++--- .../json-schema-generator/index.tsx | 156 ++++++++------- .../toolbar/__tests__/color-picker.spec.tsx | 22 +-- .../__tests__/font-size-selector.spec.tsx | 10 + .../toolbar/__tests__/index.spec.tsx | 9 +- .../note-editor/toolbar/color-picker.tsx | 55 +++--- .../toolbar/font-size-selector.tsx | 47 +++-- .../version-history-panel/filter/index.tsx | 50 ++--- web/docs/overlay-migration.md | 4 +- web/eslint.constants.mjs | 7 - 78 files changed, 2441 insertions(+), 1893 deletions(-) create mode 100644 web/__mocks__/__tests__/base-ui-popover.spec.tsx create mode 100644 web/__mocks__/base-ui-popover.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 798ae1ec28..96af36d27a 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -129,11 +129,6 @@ "count": 3 } }, - "web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx": { "no-restricted-imports": { "count": 1 @@ -1086,21 +1081,11 @@ "count": 1 } }, - "web/app/components/base/date-and-time-picker/date-picker/index.tsx": { - "react/set-state-in-effect": { - "count": 4 - } - }, "web/app/components/base/date-and-time-picker/hooks.ts": { "react/no-unnecessary-use-prefix": { "count": 2 } }, - "web/app/components/base/date-and-time-picker/time-picker/index.tsx": { - "react/set-state-in-effect": { - "count": 2 - } - }, "web/app/components/base/date-and-time-picker/types.ts": { "erasable-syntax-only/enums": { "count": 2 @@ -1195,11 +1180,6 @@ "count": 5 } }, - "web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/features/new-feature-panel/moderation/form-generation.tsx": { "no-restricted-imports": { "count": 1 @@ -1223,11 +1203,6 @@ "count": 2 } }, - "web/app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/features/types.ts": { "erasable-syntax-only/enums": { "count": 2 @@ -1878,11 +1853,6 @@ "count": 1 } }, - "web/app/components/base/portal-to-follow-elem/index.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/prompt-editor/index.stories.tsx": { "no-console": { "count": 1 @@ -1906,11 +1876,6 @@ "count": 1 } }, - "web/app/components/base/prompt-editor/plugins/context-block/component.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/prompt-editor/plugins/context-block/index.tsx": { "no-barrel-files/no-barrel-files": { "count": 3 @@ -1940,11 +1905,6 @@ "count": 2 } }, - "web/app/components/base/prompt-editor/plugins/history-block/component.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/prompt-editor/plugins/history-block/index.tsx": { "no-barrel-files/no-barrel-files": { "count": 3 @@ -2268,16 +2228,6 @@ "count": 1 } }, - "web/app/components/datasets/common/document-picker/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/datasets/common/document-picker/preview-document-picker.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/common/image-previewer/index.tsx": { "no-irregular-whitespace": { "count": 1 @@ -2894,14 +2844,6 @@ "count": 1 } }, - "web/app/components/datasets/settings/permission-selector/index.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "react/no-missing-key": { - "count": 1 - } - }, "web/app/components/datasets/settings/summary-index-setting.tsx": { "no-restricted-imports": { "count": 1 @@ -3069,21 +3011,11 @@ "count": 1 } }, - "web/app/components/header/account-setting/api-based-extension-page/selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/header/account-setting/data-source-page-new/card.tsx": { "ts/no-explicit-any": { "count": 2 } }, - "web/app/components/header/account-setting/data-source-page-new/configure.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/header/account-setting/data-source-page-new/hooks/index.ts": { "no-barrel-files/no-barrel-files": { "count": 2 @@ -3167,19 +3099,6 @@ "count": 4 } }, - "web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx": { - "no-restricted-imports": { - "count": 2 - } - }, - "web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx": { - "no-restricted-imports": { - "count": 2 - }, - "ts/no-explicit-any": { - "count": 2 - } - }, "web/app/components/header/account-setting/model-provider-page/model-auth/config-provider.tsx": { "no-restricted-imports": { "count": 1 @@ -3411,11 +3330,6 @@ "count": 1 } }, - "web/app/components/plugins/marketplace/search-box/tags-filter.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx": { "ts/no-explicit-any": { "count": 2 @@ -3447,14 +3361,6 @@ "count": 1 } }, - "web/app/components/plugins/plugin-auth/authorized/index.tsx": { - "no-restricted-imports": { - "count": 2 - }, - "ts/no-explicit-any": { - "count": 2 - } - }, "web/app/components/plugins/plugin-auth/authorized/item.tsx": { "no-restricted-imports": { "count": 1 @@ -3510,16 +3416,6 @@ "count": 8 } }, - "web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx": { "ts/no-explicit-any": { "count": 1 @@ -3713,11 +3609,6 @@ "count": 2 } }, - "web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/plugins/plugin-detail-panel/trigger/event-detail-drawer.tsx": { "ts/no-explicit-any": { "count": 5 @@ -3756,16 +3647,6 @@ "count": 2 } }, - "web/app/components/plugins/plugin-page/filter-management/category-filter.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/plugins/plugin-page/index.tsx": { "no-restricted-imports": { "count": 1 @@ -3918,11 +3799,6 @@ "count": 1 } }, - "web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/rag-pipeline/components/rag-pipeline-header/run-mode.tsx": { "ts/no-explicit-any": { "count": 1 @@ -4248,11 +4124,6 @@ "count": 1 } }, - "web/app/components/workflow/block-selector/main.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/block-selector/market-place-plugin/action.tsx": { "react/set-state-in-effect": { "count": 1 @@ -4378,19 +4249,6 @@ "count": 1 } }, - "web/app/components/workflow/header/view-history.tsx": { - "no-restricted-imports": { - "count": 2 - } - }, - "web/app/components/workflow/header/view-workflow-history.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/workflow/hooks-store/index.ts": { "no-barrel-files/no-barrel-files": { "count": 2 @@ -5053,11 +4911,6 @@ "count": 5 } }, - "web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx": { "no-restricted-imports": { "count": 1 @@ -5306,16 +5159,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/knowledge-retrieval/default.ts": { "ts/no-explicit-any": { "count": 1 @@ -5414,17 +5257,6 @@ "count": 2 } }, - "web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx": { - "erasable-syntax-only/enums": { - "count": 1 - }, - "no-restricted-imports": { - "count": 1 - }, - "react/set-state-in-effect": { - "count": 2 - } - }, "web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx": { "no-restricted-imports": { "count": 1 @@ -5871,24 +5703,11 @@ "count": 1 } }, - "web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "react-refresh/only-export-components": { - "count": 1 - } - }, "web/app/components/workflow/note-node/note-editor/toolbar/command.tsx": { "no-restricted-imports": { "count": 1 } }, - "web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/note-node/note-editor/utils.ts": { "regexp/no-useless-quantifier": { "count": 1 @@ -6012,11 +5831,6 @@ "count": 1 } }, - "web/app/components/workflow/panel/version-history-panel/filter/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx": { "no-restricted-imports": { "count": 1 diff --git a/web/__mocks__/__tests__/base-ui-popover.spec.tsx b/web/__mocks__/__tests__/base-ui-popover.spec.tsx new file mode 100644 index 0000000000..3b5b741ca0 --- /dev/null +++ b/web/__mocks__/__tests__/base-ui-popover.spec.tsx @@ -0,0 +1,127 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import * as React from 'react' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '../base-ui-popover' + +type PopoverHarnessProps = { + useRenderElement?: boolean + preventDefaultOnTrigger?: boolean +} + +const PopoverHarness = ({ + useRenderElement = false, + preventDefaultOnTrigger = false, +}: PopoverHarnessProps) => { + const [open, setOpen] = React.useState(false) + + return ( +
+
outside
+ + { + if (preventDefaultOnTrigger) + event.preventDefault() + }} + > + toggle + + ) + : undefined} + > + fallback trigger + + } + popupProps={{ 'data-popup': 'true' } as unknown as React.HTMLAttributes} + > +
popover body
+
+
+
{open ? 'open' : 'closed'}
+
+ ) +} + +describe('base-ui-popover mock', () => { + it('should toggle popover content from the fallback trigger and expose content props', () => { + render() + + expect(screen.getByTestId('open-state')).toHaveTextContent('closed') + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() + + fireEvent.click(screen.getByTestId('popover-trigger')) + + expect(screen.getByTestId('open-state')).toHaveTextContent('open') + expect(screen.getByTestId('popover-content')).toHaveAttribute('data-placement', 'bottom-start') + expect(screen.getByTestId('popover-content')).toHaveAttribute('data-side-offset', '4') + expect(screen.getByTestId('popover-content')).toHaveAttribute('data-align-offset', '8') + expect(screen.getByTestId('popover-content')).toHaveAttribute('data-positioner', 'true') + expect(screen.getByTestId('popover-content')).toHaveAttribute('data-popup', 'true') + expect(screen.getByTestId('popover-content')).toHaveClass('custom-content') + }) + + it('should keep the popover open on inside clicks and close it on outside clicks or escape', () => { + render() + + fireEvent.click(screen.getByTestId('custom-trigger')) + expect(screen.getByTestId('open-state')).toHaveTextContent('open') + + fireEvent.mouseDown(screen.getByTestId('popover-content')) + expect(screen.getByTestId('open-state')).toHaveTextContent('open') + + fireEvent.keyDown(document, { key: 'Escape' }) + expect(screen.getByTestId('open-state')).toHaveTextContent('closed') + + fireEvent.click(screen.getByTestId('custom-trigger')) + expect(screen.getByTestId('open-state')).toHaveTextContent('open') + + fireEvent.mouseDown(screen.getByTestId('outside-area')) + expect(screen.getByTestId('open-state')).toHaveTextContent('closed') + }) + + it('should preserve rendered trigger props and respect preventDefault', () => { + render() + + fireEvent.click(screen.getByTestId('custom-trigger')) + + expect(screen.getByTestId('custom-trigger')).toHaveAttribute('data-popover-trigger', 'true') + expect(screen.getByTestId('open-state')).toHaveTextContent('closed') + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() + }) + + it('should keep the popover closed when the fallback trigger click is prevented', () => { + const handleClick = (event: React.MouseEvent) => { + event.preventDefault() + } + + render( +
+ + + fallback trigger + + +
popover body
+
+
+
, + ) + + fireEvent.click(screen.getByTestId('popover-trigger')) + + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() + }) +}) diff --git a/web/__mocks__/base-ui-popover.tsx b/web/__mocks__/base-ui-popover.tsx new file mode 100644 index 0000000000..8818f60f4e --- /dev/null +++ b/web/__mocks__/base-ui-popover.tsx @@ -0,0 +1,154 @@ +import type { ReactNode } from 'react' +import * as React from 'react' + +const PopoverContext = React.createContext({ + open: false, + onOpenChange: (_open: boolean) => {}, +}) + +type PopoverProps = { + children?: ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void +} + +type PopoverTriggerProps = React.HTMLAttributes & { + children?: ReactNode + nativeButton?: boolean + render?: React.ReactElement +} + +type PopoverContentProps = React.HTMLAttributes & { + children?: ReactNode + placement?: string + sideOffset?: number + alignOffset?: number + positionerProps?: React.HTMLAttributes + popupProps?: React.HTMLAttributes +} + +export const Popover = ({ + children, + open = false, + onOpenChange, +}: PopoverProps) => { + React.useEffect(() => { + if (!open) + return + + const handleMouseDown = (event: MouseEvent) => { + const target = event.target as Element | null + if (target?.closest?.('[data-popover-trigger="true"], [data-popover-content="true"]')) + return + + onOpenChange?.(false) + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') + onOpenChange?.(false) + } + + document.addEventListener('mousedown', handleMouseDown) + document.addEventListener('keydown', handleKeyDown) + + return () => { + document.removeEventListener('mousedown', handleMouseDown) + document.removeEventListener('keydown', handleKeyDown) + } + }, [open, onOpenChange]) + + return ( + {}), + }} + > +
+ {children} +
+
+ ) +} + +export const PopoverTrigger = ({ + children, + render, + nativeButton: _nativeButton, + onClick, + ...props +}: PopoverTriggerProps) => { + const { open, onOpenChange } = React.useContext(PopoverContext) + const node = render ?? children + + if (React.isValidElement(node)) { + const triggerElement = node as React.ReactElement> + const childProps = (triggerElement.props ?? {}) as React.HTMLAttributes & { 'data-testid'?: string } + + return React.cloneElement(triggerElement, { + ...props, + ...childProps, + 'data-testid': childProps['data-testid'] ?? 'popover-trigger', + 'data-popover-trigger': 'true', + 'onClick': (event: React.MouseEvent) => { + childProps.onClick?.(event) + onClick?.(event) + if (event.defaultPrevented) + return + onOpenChange(!open) + }, + }) + } + + return ( +
{ + onClick?.(event) + if (event.defaultPrevented) + return + onOpenChange(!open) + }} + {...props} + > + {node} +
+ ) +} + +export const PopoverContent = ({ + children, + className, + placement, + sideOffset, + alignOffset, + positionerProps, + popupProps, + ...props +}: PopoverContentProps) => { + const { open } = React.useContext(PopoverContext) + + if (!open) + return null + + return ( +
+ {children} +
+ ) +} + +export const PopoverClose = ({ children }: { children?: ReactNode }) => <>{children} +export const PopoverTitle = ({ children }: { children?: ReactNode }) => <>{children} +export const PopoverDescription = ({ children }: { children?: ReactNode }) => <>{children} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx index 29de1a1eae..6d7c178728 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx @@ -3,13 +3,13 @@ import type { FC } from 'react' import type { PopupProps } from './config-popup' import { cn } from '@langgenius/dify-ui/cn' -import * as React from 'react' -import { useCallback, useRef, useState } from 'react' import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' +import * as React from 'react' +import { useState } from 'react' import ConfigPopup from './config-popup' type Props = { @@ -25,36 +25,31 @@ const ConfigBtn: FC = ({ children, ...popupProps }) => { - const [open, doSetOpen] = useState(false) - const openRef = useRef(open) - const setOpen = useCallback((v: boolean) => { - doSetOpen(v) - openRef.current = v - }, [doSetOpen]) - - const handleTrigger = useCallback(() => { - setOpen(!openRef.current) - }, [setOpen]) + const [open, setOpen] = useState(false) if (popupProps.readOnly && !hasConfigured) return null return ( - - -
- {children} -
-
- + + {children} +
+ )} + /> + - - + + ) } export default React.memo(ConfigBtn) diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx index 057f3d03df..3b82db72db 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx @@ -1,3 +1,4 @@ +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { RiChatSettingsLine, } from '@remixicon/react' @@ -6,30 +7,29 @@ import { useTranslation } from 'react-i18next' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content' import { Message3Fill } from '@/app/components/base/icons/src/public/other' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' const ViewFormDropdown = () => { const { t } = useTranslation() const [open, setOpen] = useState(false) return ( - - setOpen(v => !v)} + + + + )} + /> + - - - - -
@@ -39,8 +39,8 @@ const ViewFormDropdown = () => {
-
-
+ + ) } diff --git a/web/app/components/base/chat/chat/citation/__tests__/popup.spec.tsx b/web/app/components/base/chat/chat/citation/__tests__/popup.spec.tsx index 3a2cec9820..1b8518e869 100644 --- a/web/app/components/base/chat/chat/citation/__tests__/popup.spec.tsx +++ b/web/app/components/base/chat/chat/citation/__tests__/popup.spec.tsx @@ -6,6 +6,8 @@ import { useDocumentDownload } from '@/service/knowledge/use-document' import { downloadUrl } from '@/utils/download' import Popup from '../popup' +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) + vi.mock('@/service/knowledge/use-document', () => ({ useDocumentDownload: vi.fn(), })) diff --git a/web/app/components/base/chat/chat/citation/popup.tsx b/web/app/components/base/chat/chat/citation/popup.tsx index 51a73bc4b6..9ea4a6b742 100644 --- a/web/app/components/base/chat/chat/citation/popup.tsx +++ b/web/app/components/base/chat/chat/citation/popup.tsx @@ -1,13 +1,9 @@ import type { FC, MouseEvent } from 'react' import type { Resources } from './index' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { Fragment, useState } from 'react' import { useTranslation } from 'react-i18next' import FileIcon from '@/app/components/base/file-icon' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import Link from '@/next/link' import { useDocumentDownload } from '@/service/knowledge/use-document' import { downloadUrl } from '@/utils/download' @@ -47,22 +43,25 @@ const Popup: FC = ({ } return ( - - setOpen(v => !v)}> -
- -
{data.documentName}
-
-
- + + +
{data.documentName}
+
+ )} + /> +
@@ -156,8 +155,8 @@ const Popup: FC = ({
- - +
+ ) } diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx index 68cddb97b0..f7fc80819e 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx @@ -1,10 +1,10 @@ import { cn } from '@langgenius/dify-ui/cn' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import InputsFormContent from '@/app/components/base/chat/embedded-chatbot/inputs-form/content' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' type Props = { iconColor?: string @@ -17,25 +17,27 @@ const ViewFormDropdown = ({ const [open, setOpen] = useState(false) return ( - - setOpen(v => !v)}> - -
- - - + +
+ + )} + /> +
-
- + + ) } diff --git a/web/app/components/base/date-and-time-picker/date-picker/__tests__/index.spec.tsx b/web/app/components/base/date-and-time-picker/date-picker/__tests__/index.spec.tsx index ea4f1bb928..d37647f358 100644 --- a/web/app/components/base/date-and-time-picker/date-picker/__tests__/index.spec.tsx +++ b/web/app/components/base/date-and-time-picker/date-picker/__tests__/index.spec.tsx @@ -3,6 +3,20 @@ import { act, fireEvent, render, screen, within } from '@testing-library/react' import dayjs from '../../utils/dayjs' import DatePicker from '../index' +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) +vi.mock('@langgenius/dify-ui/button', () => ({ + Button: ({ children, onClick, disabled, className }: { + children?: React.ReactNode + onClick?: () => void + disabled?: boolean + className?: string + }) => ( + + ), +})) + // Mock scrollIntoView beforeAll(() => { Element.prototype.scrollIntoView = vi.fn() @@ -113,14 +127,13 @@ describe('DatePicker', () => { render() openPicker() + expect(screen.getByTestId('popover')).toHaveAttribute('data-open', 'true') - // Simulate a mousedown event outside the container act(() => { document.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })) }) - // The picker should now be closed - input shows its value - // The picker should now be closed - input shows its value + expect(screen.getByTestId('popover')).toHaveAttribute('data-open', 'false') expect(screen.getByRole('textbox'))!.toBeInTheDocument() }) }) diff --git a/web/app/components/base/date-and-time-picker/date-picker/index.tsx b/web/app/components/base/date-and-time-picker/date-picker/index.tsx index 9c84e4c096..7858fa2fbe 100644 --- a/web/app/components/base/date-and-time-picker/date-picker/index.tsx +++ b/web/app/components/base/date-and-time-picker/date-picker/index.tsx @@ -1,14 +1,10 @@ import type { Dayjs } from 'dayjs' import type { DatePickerProps, Period } from '../types' import { cn } from '@langgenius/dify-ui/cn' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import Calendar from '../calendar' import TimePickerHeader from '../time-picker/header' import TimePickerOptions from '../time-picker/options' @@ -35,15 +31,14 @@ const DatePicker = ({ needTimePicker = true, renderTrigger, triggerWrapClassName, - popupZIndexClassname = 'z-11', + popupZIndexClassname, noConfirm, getIsDateDisabled, }: DatePickerProps) => { const { t } = useTranslation() const [isOpen, setIsOpen] = useState(false) const [view, setView] = useState(ViewType.date) - const containerRef = useRef(null) - const isInitial = useRef(true) + const isInitialRef = useRef(true) // Normalize the value to ensure that all subsequent uses are Day.js objects. const normalizedValue = useMemo(() => { @@ -62,46 +57,41 @@ const DatePicker = ({ const [selectedYear, setSelectedYear] = useState(() => (inputValue || defaultValue).year()) useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (containerRef.current && !containerRef.current.contains(event.target as Node)) { - setIsOpen(false) - setView(ViewType.date) - } - } - document.addEventListener('mousedown', handleClickOutside) - return () => document.removeEventListener('mousedown', handleClickOutside) - }, []) - - useEffect(() => { - if (isInitial.current) { - isInitial.current = false + if (isInitialRef.current) { + isInitialRef.current = false return } clearMonthMapCache() if (normalizedValue) { const newValue = getDateWithTimezone({ date: normalizedValue, timezone }) + // eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the displayed calendar state. setCurrentDate(newValue) + // eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the selected value. setSelectedDate(newValue) onChange(newValue) } else { + // eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the displayed calendar state. setCurrentDate(prev => getDateWithTimezone({ date: prev, timezone })) + // eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the selected value. setSelectedDate(prev => prev ? getDateWithTimezone({ date: prev, timezone }) : undefined) } + // eslint-disable-next-line react/exhaustive-deps -- this effect intentionally runs only when timezone changes. }, [timezone]) - const handleClickTrigger = (e: React.MouseEvent) => { - e.stopPropagation() - if (isOpen) { - setIsOpen(false) - return - } + const handleOpenChange = useCallback((nextOpen: boolean) => { + setIsOpen(nextOpen) setView(ViewType.date) - setIsOpen(true) - if (normalizedValue) { + if (nextOpen && normalizedValue) { setCurrentDate(normalizedValue) setSelectedDate(normalizedValue) } + }, [normalizedValue]) + + const handleClickTrigger = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + handleOpenChange(!isOpen) } const handleClear = (e: React.MouseEvent) => { @@ -210,21 +200,21 @@ const DatePicker = ({ const placeholderDate = isOpen && selectedDate ? selectedDate.format(timeFormat) : (placeholder || t('defaultPlaceholder', { ns: 'time' })) return ( - - - {renderTrigger - ? ( - renderTrigger({ - value: normalizedValue, - selectedDate, - isOpen, - handleClear, - handleClickTrigger, - })) +
)} -
- + /> +
{/* Header */} {view === ViewType.date @@ -319,8 +314,8 @@ const DatePicker = ({ ) }
-
-
+ + ) } diff --git a/web/app/components/base/date-and-time-picker/time-picker/__tests__/index.spec.tsx b/web/app/components/base/date-and-time-picker/time-picker/__tests__/index.spec.tsx index 7fbed3a736..0d02b3b5d5 100644 --- a/web/app/components/base/date-and-time-picker/time-picker/__tests__/index.spec.tsx +++ b/web/app/components/base/date-and-time-picker/time-picker/__tests__/index.spec.tsx @@ -3,6 +3,20 @@ import { fireEvent, render, screen, within } from '@testing-library/react' import dayjs, { isDayjsObject } from '../../utils/dayjs' import TimePicker from '../index' +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) +vi.mock('@langgenius/dify-ui/button', () => ({ + Button: ({ children, onClick, disabled, className }: { + children?: React.ReactNode + onClick?: () => void + disabled?: boolean + className?: string + }) => ( + + ), +})) + // Mock scrollIntoView since the test DOM runtime doesn't implement it beforeAll(() => { Element.prototype.scrollIntoView = vi.fn() @@ -106,7 +120,7 @@ describe('TimePicker', () => { expect(input)!.toHaveValue('') fireEvent.mouseDown(document.body) - expect(input)!.toHaveValue('') + expect(input)!.toHaveValue('10:00 AM') }) it('should call onClear when clear is clicked while picker is closed', () => { diff --git a/web/app/components/base/date-and-time-picker/time-picker/index.tsx b/web/app/components/base/date-and-time-picker/time-picker/index.tsx index e07ea177a5..3fcb88215e 100644 --- a/web/app/components/base/date-and-time-picker/time-picker/index.tsx +++ b/web/app/components/base/date-and-time-picker/time-picker/index.tsx @@ -1,14 +1,10 @@ import type { Dayjs } from 'dayjs' import type { TimePickerProps } from '../types' import { cn } from '@langgenius/dify-ui/cn' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import * as React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import TimezoneLabel from '@/app/components/base/timezone-label' import { Period } from '../types' import dayjs, { @@ -43,31 +39,20 @@ const TimePicker = ({ }: TimePickerProps) => { const { t } = useTranslation() const [isOpen, setIsOpen] = useState(false) - const containerRef = useRef(null) - const isInitial = useRef(true) + const isInitialRef = useRef(true) // Initialize selectedTime const [selectedTime, setSelectedTime] = useState(() => { return toDayjs(value, { timezone }) }) - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - /* v8 ignore next 2 -- outside-click closing is handled by PortalToFollowElem; this local ref guard is a defensive fallback. */ - if (containerRef.current && !containerRef.current.contains(event.target as Node)) - setIsOpen(false) - } - document.addEventListener('mousedown', handleClickOutside) - return () => document.removeEventListener('mousedown', handleClickOutside) - }, []) - // Track previous values to avoid unnecessary updates const prevValueRef = useRef(value) const prevTimezoneRef = useRef(timezone) useEffect(() => { - if (isInitial.current) { - isInitial.current = false + if (isInitialRef.current) { + isInitialRef.current = false // Save initial values on first render prevValueRef.current = value prevTimezoneRef.current = timezone @@ -91,6 +76,7 @@ const TimePicker = ({ if (!dayjsValue) return + // eslint-disable-next-line react/set-state-in-effect -- value/timezone changes intentionally resync the internal selected time. setSelectedTime(dayjsValue) if (timezoneChanged && !valueChanged) @@ -98,6 +84,7 @@ const TimePicker = ({ return } + // eslint-disable-next-line react/set-state-in-effect -- value/timezone changes intentionally resync the internal selected time. setSelectedTime((prev) => { if (!isDayjsObject(prev)) return undefined @@ -105,24 +92,30 @@ const TimePicker = ({ }) }, [timezone, value, onChange]) - const handleClickTrigger = (e: React.MouseEvent) => { - e.stopPropagation() - if (isOpen) { - setIsOpen(false) + const syncSelectedTimeFromValue = useCallback(() => { + if (!value) return - } - setIsOpen(true) - if (value) { - const dayjsValue = toDayjs(value, { timezone }) - const needsUpdate = dayjsValue && ( - !selectedTime - || !isDayjsObject(selectedTime) - || !dayjsValue.isSame(selectedTime, 'minute') - ) - if (needsUpdate) - setSelectedTime(dayjsValue) - } + const dayjsValue = toDayjs(value, { timezone }) + const needsUpdate = dayjsValue && ( + !selectedTime + || !isDayjsObject(selectedTime) + || !dayjsValue.isSame(selectedTime, 'minute') + ) + if (needsUpdate) + setSelectedTime(dayjsValue) + }, [selectedTime, timezone, value]) + + const handleOpenChange = useCallback((nextOpen: boolean) => { + setIsOpen(nextOpen) + if (nextOpen) + syncSelectedTimeFromValue() + }, [syncSelectedTimeFromValue]) + + const handleClickTrigger = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + handleOpenChange(!isOpen) } const handleClear = (e: React.MouseEvent) => { @@ -132,7 +125,7 @@ const TimePicker = ({ onClear() } - const handleTimeSelect = (hour: string, minute: string, period: Period) => { + const handleTimeSelect = useCallback((hour: string, minute: string, period: Period) => { const periodAdjustedHour = to24Hour(hour, period) const nextMinute = Number.parseInt(minute, 10) setSelectedTime((prev) => { @@ -145,7 +138,7 @@ const TimePicker = ({ .set('second', 0) .set('millisecond', 0) }) - } + }, [timezone]) const getSafeTimeObject = useCallback(() => { if (isDayjsObject(selectedTime)) @@ -156,17 +149,17 @@ const TimePicker = ({ const handleSelectHour = useCallback((hour: string) => { const time = getSafeTimeObject() handleTimeSelect(hour, time.minute().toString().padStart(2, '0'), time.format('A') as Period) - }, [getSafeTimeObject]) + }, [getSafeTimeObject, handleTimeSelect]) const handleSelectMinute = useCallback((minute: string) => { const time = getSafeTimeObject() handleTimeSelect(getHourIn12Hour(time).toString().padStart(2, '0'), minute, time.format('A') as Period) - }, [getSafeTimeObject]) + }, [getSafeTimeObject, handleTimeSelect]) const handleSelectPeriod = useCallback((period: Period) => { const time = getSafeTimeObject() handleTimeSelect(getHourIn12Hour(time).toString().padStart(2, '0'), time.minute().toString().padStart(2, '0'), period) - }, [getSafeTimeObject]) + }, [getSafeTimeObject, handleTimeSelect]) const handleSelectCurrentTime = useCallback(() => { const newDate = getDateWithTimezone({ timezone }) @@ -207,18 +200,19 @@ const TimePicker = ({ /> ) return ( - - - {renderTrigger - ? (renderTrigger({ +
)} - - + /> +
{/* Header */}
@@ -258,8 +257,8 @@ const TimePicker = ({ />
-
- + + ) } diff --git a/web/app/components/base/date-and-time-picker/types.ts b/web/app/components/base/date-and-time-picker/types.ts index 0068ec22ac..2773fb7bc7 100644 --- a/web/app/components/base/date-and-time-picker/types.ts +++ b/web/app/components/base/date-and-time-picker/types.ts @@ -28,7 +28,7 @@ export type DatePickerProps = { onChange: (date: Dayjs | undefined) => void onClear: () => void triggerWrapClassName?: string - renderTrigger?: (props: TriggerProps) => React.ReactNode + renderTrigger?: (props: TriggerProps) => React.ReactElement minuteFilter?: (minutes: string[]) => string[] popupZIndexClassname?: string noConfirm?: boolean @@ -62,7 +62,7 @@ export type TimePickerProps = { placeholder?: string onChange: (date: Dayjs | undefined) => void onClear: () => void - renderTrigger?: (props: TriggerParams) => React.ReactNode + renderTrigger?: (props: TriggerParams) => React.ReactElement title?: string minuteFilter?: (minutes: string[]) => string[] popupClassName?: string diff --git a/web/app/components/base/features/new-feature-panel/file-upload/__tests__/setting-modal.spec.tsx b/web/app/components/base/features/new-feature-panel/file-upload/__tests__/setting-modal.spec.tsx index dc111a680b..6259c7cb4f 100644 --- a/web/app/components/base/features/new-feature-panel/file-upload/__tests__/setting-modal.spec.tsx +++ b/web/app/components/base/features/new-feature-panel/file-upload/__tests__/setting-modal.spec.tsx @@ -61,7 +61,7 @@ describe('FileUploadSettings (setting-modal)', () => { }) }) - it('should call onOpen with toggle function when trigger is clicked', () => { + it('should call onOpen with true when trigger is clicked', () => { const onOpen = vi.fn() renderWithProvider( @@ -71,12 +71,7 @@ describe('FileUploadSettings (setting-modal)', () => { fireEvent.click(screen.getByText('Upload Settings')) - expect(onOpen).toHaveBeenCalled() - // The toggle function should flip the open state - const toggleFn = onOpen.mock.calls[0]![0] - expect(typeof toggleFn).toBe('function') - expect(toggleFn(false)).toBe(true) - expect(toggleFn(true)).toBe(false) + expect(onOpen).toHaveBeenCalledWith(true) }) it('should not call onOpen when disabled', () => { diff --git a/web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx b/web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx index 2a09f63bee..a1c6bffbe0 100644 --- a/web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx +++ b/web/app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx @@ -1,16 +1,16 @@ 'use client' import type { OnFeaturesChange } from '@/app/components/base/features/types' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { memo } from 'react' import SettingContent from '@/app/components/base/features/new-feature-panel/file-upload/setting-content' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' type FileUploadSettingsProps = { open: boolean - onOpen: (state: any) => void + onOpen: (state: boolean) => void onChange?: OnFeaturesChange disabled?: boolean children?: React.ReactNode @@ -25,18 +25,27 @@ const FileUploadSettings = ({ imageUpload, }: FileUploadSettingsProps) => { return ( - { + if (disabled) + return + onOpen(nextOpen) }} > - !disabled && onOpen((open: boolean) => !open)}> - {children} - - + + {children} + + )} + /> +
-
-
+ + ) } export default memo(FileUploadSettings) diff --git a/web/app/components/base/features/new-feature-panel/text-to-speech/__tests__/voice-settings.spec.tsx b/web/app/components/base/features/new-feature-panel/text-to-speech/__tests__/voice-settings.spec.tsx index 574aeddd4a..b2dd37d1e8 100644 --- a/web/app/components/base/features/new-feature-panel/text-to-speech/__tests__/voice-settings.spec.tsx +++ b/web/app/components/base/features/new-feature-panel/text-to-speech/__tests__/voice-settings.spec.tsx @@ -1,38 +1,17 @@ +import type { ReactNode } from 'react' import type { Features } from '../../../types' import { fireEvent, render, screen } from '@testing-library/react' import { FeaturesProvider } from '../../../context' import VoiceSettings from '../voice-settings' -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ - children, - placement, - offset, - }: { - children: React.ReactNode - placement?: string - offset?: { mainAxis?: number } - }) => ( -
- {children} -
- ), - PortalToFollowElemTrigger: ({ - children, - onClick, - }: { - children: React.ReactNode - onClick?: () => void - }) => ( -
- {children} -
- ), - PortalToFollowElemContent: ({ children }: { children: React.ReactNode }) =>
{children}
, +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) +vi.mock('@langgenius/dify-ui/toast', () => ({ + toast: { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + }, })) vi.mock('@/next/navigation', () => ({ @@ -46,6 +25,25 @@ vi.mock('@/service/use-apps', () => ({ }), })) +vi.mock('@langgenius/dify-ui/switch', () => ({ + Switch: ({ + checked, + onCheckedChange, + ...props + }: { + checked?: boolean + onCheckedChange?: (checked: boolean) => void + }) => ( + , ) - const portal = screen.getAllByTestId('voice-settings-portal') - .find(item => item.hasAttribute('data-main-axis')) - - expect(portal).toBeDefined() - expect(portal)!.toHaveAttribute('data-placement', 'top') - expect(portal)!.toHaveAttribute('data-main-axis', '4') + const content = screen.getByTestId('popover-content') + expect(content).toHaveAttribute('data-placement', 'top') + expect(content).toHaveAttribute('data-side-offset', '4') }) }) diff --git a/web/app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx b/web/app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx index 3717c76352..7375733299 100644 --- a/web/app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx +++ b/web/app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx @@ -1,16 +1,16 @@ 'use client' import type { OnFeaturesChange } from '@/app/components/base/features/types' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { memo } from 'react' import ParamConfigContent from '@/app/components/base/features/new-feature-panel/text-to-speech/param-config-content' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' type VoiceSettingsProps = { open: boolean - onOpen: (state: any) => void + onOpen: (state: boolean) => void onChange?: OnFeaturesChange disabled?: boolean children?: React.ReactNode @@ -25,23 +25,32 @@ const VoiceSettings = ({ placementLeft = true, }: VoiceSettingsProps) => { return ( - { + if (disabled) + return + onOpen(nextOpen) }} > - !disabled && onOpen((open: boolean) => !open)}> - {children} - - + + {children} + + )} + /> +
onOpen(false)} onChange={onChange} />
-
-
+ + ) } export default memo(VoiceSettings) diff --git a/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx b/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx index 912c2d3c48..27c6c36f6c 100644 --- a/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx +++ b/web/app/components/base/file-uploader/file-from-link-or-local/index.tsx @@ -1,17 +1,17 @@ import type { FileUpload } from '@/app/components/base/features/types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiUploadCloud2Line } from '@remixicon/react' import { memo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { FILE_URL_REGEX } from '../constants' import FileInput from '../file-input' import { useFile } from '../hooks' @@ -54,16 +54,16 @@ const FileFromLinkOrLocal = ({ } return ( - - setOpen(v => !v)} asChild> - {trigger(open)} - - + +
{ showFromLink && ( @@ -126,8 +126,8 @@ const FileFromLinkOrLocal = ({ ) }
-
-
+ + ) } diff --git a/web/app/components/base/image-uploader/chat-image-uploader.tsx b/web/app/components/base/image-uploader/chat-image-uploader.tsx index b2f6a106c6..5093b33bd0 100644 --- a/web/app/components/base/image-uploader/chat-image-uploader.tsx +++ b/web/app/components/base/image-uploader/chat-image-uploader.tsx @@ -1,13 +1,13 @@ import type { FC } from 'react' import type { ImageFile, VisionSettings } from '@/types/app' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { TransferMethod } from '@/types/app' import ImageLinkInput from './image-link-input' import Uploader from './uploader' @@ -63,29 +63,31 @@ const UploaderButton: FC = ({ const closePopover = () => setOpen(false) - const handleToggle = () => { - if (disabled) - return - - setOpen(v => !v) - } - return ( - { + if (disabled) + return + setOpen(nextOpen) + }} > - - - - + + + + )} + /> +
{!!hasUploadFromLocal && ( @@ -115,8 +117,8 @@ const UploaderButton: FC = ({ )}
-
-
+ + ) } diff --git a/web/app/components/base/image-uploader/text-generation-image-uploader.tsx b/web/app/components/base/image-uploader/text-generation-image-uploader.tsx index 1b986744f2..af05eb2a35 100644 --- a/web/app/components/base/image-uploader/text-generation-image-uploader.tsx +++ b/web/app/components/base/image-uploader/text-generation-image-uploader.tsx @@ -1,5 +1,10 @@ import type { FC } from 'react' import type { ImageFile, VisionSettings } from '@/types/app' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { Fragment, useEffect, @@ -8,11 +13,6 @@ import { import { useTranslation } from 'react-i18next' import { Link03 } from '@/app/components/base/icons/src/vender/line/general' import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { TransferMethod } from '@/types/app' import { useImageFiles } from './hooks' import ImageLinkInput from './image-link-input' @@ -35,35 +35,38 @@ const PasteImageLinkButton: FC = ({ onUpload(imageFile) } - const handleToggle = () => { - if (disabled) - return - - setOpen(v => !v) - } - return ( - { + if (disabled) + return + setOpen(nextOpen) + }} > - -
- - {t('imageUploader.pasteImageLink', { ns: 'common' })} -
-
- + + + {t('imageUploader.pasteImageLink', { ns: 'common' })} + + )} + /> +
-
-
+ + ) } diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx index 8b531be309..fbadeaf302 100644 --- a/web/app/components/base/portal-to-follow-elem/index.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.tsx @@ -148,14 +148,17 @@ export const PortalToFollowElemTrigger = ( }: React.HTMLProps & { ref?: React.RefObject, asChild?: boolean }, ) => { const context = usePortalToFollowElemContext() - const childrenRef = (children as any).props?.ref + const childElement = React.isValidElement<{ ref?: React.Ref }>(children) + ? children + : null + const childrenRef = childElement?.props.ref const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef]) // `asChild` allows the user to pass any element as the anchor - if (asChild && React.isValidElement(children)) { - const childProps = (children.props ?? {}) as Record + if (asChild && childElement) { + const childProps = (childElement.props ?? {}) as Record return React.cloneElement( - children, + childElement, context.getReferenceProps({ ref, ...props, diff --git a/web/app/components/base/prompt-editor/plugins/context-block/__tests__/component.spec.tsx b/web/app/components/base/prompt-editor/plugins/context-block/__tests__/component.spec.tsx index eb011af528..64bca0a24d 100644 --- a/web/app/components/base/prompt-editor/plugins/context-block/__tests__/component.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/context-block/__tests__/component.spec.tsx @@ -2,6 +2,9 @@ import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { UPDATE_DATASETS_EVENT_EMITTER } from '../../../constants' import ContextBlockComponent from '../component' + +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) + // Mock the hooks used by ContextBlockComponent const mockUseSelectOrDelete = vi.fn() const mockUseTrigger = vi.fn() @@ -223,6 +226,21 @@ describe('ContextBlockComponent', () => { }) describe('User Interactions', () => { + it('should keep the popover closed when the trigger prevents the default click', async () => { + const user = userEvent.setup() + const { triggerSetOpen } = defaultSetup() + render( + , + ) + + await user.click(screen.getByTestId('popover-trigger')) + + expect(triggerSetOpen).not.toHaveBeenCalled() + expect( + screen.queryByText('common.promptEditor.context.modal.add'), + ).not.toBeInTheDocument() + }) + it('should call onAddContext when add button is clicked', async () => { defaultSetup({ open: true }) const handleAddContext = vi.fn() @@ -345,6 +363,29 @@ describe('ContextBlockComponent', () => { // Original datasets still there expect(screen.getByText('Dataset A')).toBeInTheDocument() }) + + it('should ignore string events from the event emitter', () => { + defaultSetup({ open: true }) + let subscriptionCallback: (v: Record | string) => void = () => { } + mockUseSubscription.mockImplementation((cb: (v: Record | string) => void) => { + subscriptionCallback = cb + }) + + render( + , + ) + + act(() => { + subscriptionCallback('ignore-me') + }) + + expect(screen.getByText('Dataset A')).toBeInTheDocument() + expect(screen.getByText('Dataset B')).toBeInTheDocument() + }) }) describe('Edge Cases', () => { diff --git a/web/app/components/base/prompt-editor/plugins/context-block/component.tsx b/web/app/components/base/prompt-editor/plugins/context-block/component.tsx index 35f6948e07..05fff5b4e8 100644 --- a/web/app/components/base/prompt-editor/plugins/context-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/context-block/component.tsx @@ -1,13 +1,10 @@ import type { FC } from 'react' import type { Dataset } from './index' +import type { EventEmitterValue } from '@/context/event-emitter' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useEventEmitterContextContext } from '@/context/event-emitter' import { UPDATE_DATASETS_EVENT_EMITTER } from '../../constants' import { useSelectOrDelete, useTrigger } from '../../hooks' @@ -32,9 +29,12 @@ const ContextBlockComponent: FC = ({ const { eventEmitter } = useEventEmitterContextContext() const [localDatasets, setLocalDatasets] = useState(datasets) - eventEmitter?.useSubscription((v: any) => { - if (v?.type === UPDATE_DATASETS_EVENT_EMITTER) - setLocalDatasets(v.payload) + eventEmitter?.useSubscription((event?: EventEmitterValue) => { + if (typeof event === 'string') + return + + if (event?.type === UPDATE_DATASETS_EVENT_EMITTER && Array.isArray(event.payload)) + setLocalDatasets(event.payload as Dataset[]) }) return ( @@ -49,24 +49,31 @@ const ContextBlockComponent: FC = ({
{t('promptEditor.context.item.title', { ns: 'common' })}
{!canNotAddContext && ( - - -
- {localDatasets.length} -
-
- + `} + ref={triggerRef} + onClick={e => e.preventDefault()} + > + {localDatasets.length} + + )} + /> +
@@ -95,8 +102,8 @@ const ContextBlockComponent: FC = ({ {t('promptEditor.context.modal.footer', { ns: 'common' })}
- - + + )}
diff --git a/web/app/components/base/prompt-editor/plugins/history-block/__tests__/component.spec.tsx b/web/app/components/base/prompt-editor/plugins/history-block/__tests__/component.spec.tsx index aa4f0a85ca..4d57498424 100644 --- a/web/app/components/base/prompt-editor/plugins/history-block/__tests__/component.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/history-block/__tests__/component.spec.tsx @@ -6,6 +6,8 @@ import { UPDATE_HISTORY_EVENT_EMITTER } from '../../../constants' import HistoryBlockComponent from '../component' import { DELETE_HISTORY_BLOCK_COMMAND } from '../index' +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) + type HistoryEventPayload = { type?: string payload?: RoleName @@ -109,6 +111,24 @@ describe('HistoryBlockComponent', () => { expect(screen.getByText('common.promptEditor.history.modal.assistant')).toBeInTheDocument() }) + it('should keep the popover closed when the trigger prevents the default click', async () => { + const user = userEvent.setup() + const setOpen = vi.fn() as unknown as Dispatch> + mockUseTrigger.mockReturnValue(createTriggerHookReturn(false, setOpen)) + + render( + , + ) + + await user.click(screen.getByTestId('popover-trigger')) + + expect(setOpen).not.toHaveBeenCalled() + expect(screen.queryByText('common.promptEditor.history.modal.edit')).not.toBeInTheDocument() + }) + it('should call onEditRole when edit action is clicked', async () => { const user = userEvent.setup() const onEditRole = vi.fn() @@ -188,6 +208,29 @@ describe('HistoryBlockComponent', () => { expect(screen.getByText('kept-assistant')).toBeInTheDocument() }) + it('should ignore string events from the event emitter', () => { + mockUseTrigger.mockReturnValue(createTriggerHookReturn(true)) + + render( + , + ) + + expect(subscribedHandler).not.toBeNull() + act(() => { + subscribedHandler?.('ignore-me' as unknown as HistoryEventPayload) + }) + + expect(screen.getByText('kept-user')).toBeInTheDocument() + expect(screen.getByText('kept-assistant')).toBeInTheDocument() + }) + it('should render when event emitter is unavailable', () => { mockUseEventEmitterContextContext.mockReturnValue({ eventEmitter: undefined, diff --git a/web/app/components/base/prompt-editor/plugins/history-block/component.tsx b/web/app/components/base/prompt-editor/plugins/history-block/component.tsx index 15ce102bd9..a9bc68ac30 100644 --- a/web/app/components/base/prompt-editor/plugins/history-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/history-block/component.tsx @@ -1,16 +1,13 @@ import type { FC } from 'react' import type { RoleName } from './index' +import type { EventEmitterValue } from '@/context/event-emitter' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { RiMoreFill, } from '@remixicon/react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useEventEmitterContextContext } from '@/context/event-emitter' import { UPDATE_HISTORY_EVENT_EMITTER } from '../../constants' import { useSelectOrDelete, useTrigger } from '../../hooks' @@ -33,9 +30,12 @@ const HistoryBlockComponent: FC = ({ const { eventEmitter } = useEventEmitterContextContext() const [localRoleName, setLocalRoleName] = useState(roleName) - eventEmitter?.useSubscription((v: any) => { - if (v?.type === UPDATE_HISTORY_EVENT_EMITTER) - setLocalRoleName(v.payload) + eventEmitter?.useSubscription((event?: EventEmitterValue) => { + if (typeof event === 'string') + return + + if (event?.type === UPDATE_HISTORY_EVENT_EMITTER && event.payload && typeof event.payload === 'object') + setLocalRoleName(event.payload as RoleName) }) return ( @@ -49,25 +49,31 @@ const HistoryBlockComponent: FC = ({ >
{t('promptEditor.history.item.title', { ns: 'common' })}
- - -
- -
-
- + ref={triggerRef} + onClick={e => e.preventDefault()} + > + + + )} + /> +
{t('promptEditor.history.modal.title', { ns: 'common' })}
@@ -87,8 +93,8 @@ const HistoryBlockComponent: FC = ({ {t('promptEditor.history.modal.edit', { ns: 'common' })}
-
-
+
+ ) } diff --git a/web/app/components/datasets/common/document-picker/__tests__/index.spec.tsx b/web/app/components/datasets/common/document-picker/__tests__/index.spec.tsx index f8f0ce6e12..1251eab9fb 100644 --- a/web/app/components/datasets/common/document-picker/__tests__/index.spec.tsx +++ b/web/app/components/datasets/common/document-picker/__tests__/index.spec.tsx @@ -5,34 +5,7 @@ import * as React from 'react' import { ChunkingMode, DataSourceType } from '@/models/datasets' import DocumentPicker from '../index' -// Mock portal-to-follow-elem - always render content for testing -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open }: { - children: React.ReactNode - open?: boolean - }) => ( -
- {children} -
- ), - PortalToFollowElemTrigger: ({ children, onClick }: { - children: React.ReactNode - onClick?: () => void - }) => ( -
- {children} -
- ), - // Always render content to allow testing document selection - PortalToFollowElemContent: ({ children, className }: { - children: React.ReactNode - className?: string - }) => ( -
- {children} -
- ), -})) +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) // Mock useDocumentList hook with controllable return value let mockDocumentListData: { data: SimpleDocumentDetail[] } | undefined @@ -152,6 +125,10 @@ const renderComponent = (props: Partial { + fireEvent.click(screen.getByTestId('popover-trigger')) +} + describe('DocumentPicker', () => { beforeEach(() => { vi.clearAllMocks() @@ -165,7 +142,7 @@ describe('DocumentPicker', () => { it('should render without crashing', () => { renderComponent() - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should render document name when provided', () => { @@ -273,7 +250,7 @@ describe('DocumentPicker', () => { onChange, }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle value with all fields', () => { @@ -318,13 +295,13 @@ describe('DocumentPicker', () => { it('should initialize with popup closed', () => { renderComponent() - expect(screen.getByTestId('portal-elem')).toHaveAttribute('data-open', 'false') + expect(screen.getByTestId('popover')).toHaveAttribute('data-open', 'false') }) it('should open popup when trigger is clicked', () => { renderComponent() - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') fireEvent.click(trigger) // Verify click handler is called @@ -430,7 +407,7 @@ describe('DocumentPicker', () => { ) // The component should use the new callback - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should memoize handleChange callback with useCallback', () => { @@ -440,7 +417,7 @@ describe('DocumentPicker', () => { renderComponent({ onChange }) // Verify component renders correctly, callback memoization is internal - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -518,7 +495,7 @@ describe('DocumentPicker', () => { it('should toggle popup when trigger is clicked', () => { renderComponent() - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') fireEvent.click(trigger) // Trigger click should be handled @@ -591,7 +568,7 @@ describe('DocumentPicker', () => { renderComponent() // When loading, component should still render without crashing - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should fetch documents on mount', () => { @@ -611,7 +588,7 @@ describe('DocumentPicker', () => { renderComponent() // Component should render without crashing - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle undefined data response', () => { @@ -620,7 +597,7 @@ describe('DocumentPicker', () => { renderComponent() // Should not crash - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -732,13 +709,13 @@ describe('DocumentPicker', () => { renderComponent() // Should not crash - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle rapid toggle clicks', () => { renderComponent() - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') // Rapid clicks fireEvent.click(trigger) @@ -795,7 +772,7 @@ describe('DocumentPicker', () => { renderComponent() // Should not crash - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle document list mapping with various data_source_detail_dict states', () => { @@ -819,7 +796,7 @@ describe('DocumentPicker', () => { renderComponent() // Should not crash during mapping - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -829,13 +806,13 @@ describe('DocumentPicker', () => { it('should handle empty datasetId', () => { renderComponent({ datasetId: '' }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle UUID format datasetId', () => { renderComponent({ datasetId: '123e4567-e89b-12d3-a456-426614174000' }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -926,6 +903,7 @@ describe('DocumentPicker', () => { const onChange = vi.fn() renderComponent({ onChange }) + openPopover() fireEvent.click(screen.getByText('Document 2')) @@ -939,6 +917,7 @@ describe('DocumentPicker', () => { mockDocumentListData = { data: docs } renderComponent() + openPopover() // Documents should be rendered in the list expect(screen.getByText('Document 1')).toBeInTheDocument() @@ -978,14 +957,14 @@ describe('DocumentPicker', () => { // The mapping: d.data_source_detail_dict?.upload_file?.extension || '' // Should extract 'pdf' from the document - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should render trigger with SearchInput integration', () => { renderComponent() // The trigger is always rendered - expect(screen.getByTestId('portal-trigger')).toBeInTheDocument() + expect(screen.getByTestId('popover-trigger')).toBeInTheDocument() }) it('should integrate FileIcon component', () => { @@ -1001,7 +980,7 @@ describe('DocumentPicker', () => { }) // FileIcon should render an SVG icon for the file extension - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') expect(trigger.querySelector('svg')).toBeInTheDocument() }) }) @@ -1010,9 +989,10 @@ describe('DocumentPicker', () => { describe('Visual States', () => { it('should render portal content for document selection', () => { renderComponent() + openPopover() - // Portal content is rendered in our mock for testing - expect(screen.getByTestId('portal-content')).toBeInTheDocument() + // Popover content is rendered after opening the trigger in our mock + expect(screen.getByTestId('popover-content')).toBeInTheDocument() }) }) }) diff --git a/web/app/components/datasets/common/document-picker/__tests__/preview-document-picker.spec.tsx b/web/app/components/datasets/common/document-picker/__tests__/preview-document-picker.spec.tsx index 7178e9f60c..c7eb2c740c 100644 --- a/web/app/components/datasets/common/document-picker/__tests__/preview-document-picker.spec.tsx +++ b/web/app/components/datasets/common/document-picker/__tests__/preview-document-picker.spec.tsx @@ -3,34 +3,7 @@ import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' import PreviewDocumentPicker from '../preview-document-picker' -// Mock portal-to-follow-elem - always render content for testing -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open }: { - children: React.ReactNode - open?: boolean - }) => ( -
- {children} -
- ), - PortalToFollowElemTrigger: ({ children, onClick }: { - children: React.ReactNode - onClick?: () => void - }) => ( -
- {children} -
- ), - // Always render content to allow testing document selection - PortalToFollowElemContent: ({ children, className }: { - children: React.ReactNode - className?: string - }) => ( -
- {children} -
- ), -})) +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) // Factory function to create mock DocumentItem const createMockDocumentItem = (overrides: Partial = {}): DocumentItem => ({ @@ -67,6 +40,10 @@ const renderComponent = (props: Partial { + fireEvent.click(screen.getByTestId('popover-trigger')) +} + describe('PreviewDocumentPicker', () => { beforeEach(() => { vi.clearAllMocks() @@ -77,7 +54,7 @@ describe('PreviewDocumentPicker', () => { it('should render without crashing', () => { renderComponent() - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should render document name from value prop', () => { @@ -110,7 +87,7 @@ describe('PreviewDocumentPicker', () => { files: [], // Use empty files to avoid duplicate icons }) - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') expect(trigger.querySelector('svg')).toBeInTheDocument() }) @@ -120,7 +97,7 @@ describe('PreviewDocumentPicker', () => { files: [], // Use empty files to avoid duplicate icons }) - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') expect(trigger.querySelector('svg')).toBeInTheDocument() }) }) @@ -131,22 +108,21 @@ describe('PreviewDocumentPicker', () => { const props = createDefaultProps() render() - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should apply className to trigger element', () => { renderComponent({ className: 'custom-class' }) - const trigger = screen.getByTestId('portal-trigger') - const innerDiv = trigger.querySelector('.custom-class') - expect(innerDiv).toBeInTheDocument() + const trigger = screen.getByTestId('popover-trigger') + expect(trigger).toHaveClass('custom-class') }) it('should handle empty files array', () => { // Component should render without crashing with empty files renderComponent({ files: [] }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle single file', () => { @@ -155,7 +131,7 @@ describe('PreviewDocumentPicker', () => { files: [createMockDocumentItem({ id: 'single-doc', name: 'Single File' })], }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle multiple files', () => { @@ -164,7 +140,7 @@ describe('PreviewDocumentPicker', () => { files: createMockDocumentList(5), }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should use value.extension for file icon', () => { @@ -172,7 +148,7 @@ describe('PreviewDocumentPicker', () => { value: createMockDocumentItem({ name: 'test.docx', extension: 'docx' }), }) - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') expect(trigger.querySelector('svg')).toBeInTheDocument() }) }) @@ -182,13 +158,13 @@ describe('PreviewDocumentPicker', () => { it('should initialize with popup closed', () => { renderComponent() - expect(screen.getByTestId('portal-elem')).toHaveAttribute('data-open', 'false') + expect(screen.getByTestId('popover')).toHaveAttribute('data-open', 'false') }) it('should toggle popup when trigger is clicked', () => { renderComponent() - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') fireEvent.click(trigger) expect(trigger).toBeInTheDocument() @@ -196,9 +172,10 @@ describe('PreviewDocumentPicker', () => { it('should render portal content for document selection', () => { renderComponent() + openPopover() - // Portal content is always rendered in our mock for testing - expect(screen.getByTestId('portal-content')).toBeInTheDocument() + // Popover content is rendered after opening the trigger in our mock + expect(screen.getByTestId('popover-content')).toBeInTheDocument() }) }) @@ -242,7 +219,7 @@ describe('PreviewDocumentPicker', () => { , ) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -265,7 +242,7 @@ describe('PreviewDocumentPicker', () => { , ) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -274,7 +251,7 @@ describe('PreviewDocumentPicker', () => { it('should toggle popup when trigger is clicked', () => { renderComponent() - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') fireEvent.click(trigger) expect(trigger).toBeInTheDocument() @@ -283,6 +260,7 @@ describe('PreviewDocumentPicker', () => { it('should render document list with files', () => { const files = createMockDocumentList(3) renderComponent({ files }) + openPopover() // Documents should be visible in the list expect(screen.getByText('Document 1')).toBeInTheDocument() @@ -295,6 +273,7 @@ describe('PreviewDocumentPicker', () => { const files = createMockDocumentList(3) renderComponent({ files, onChange }) + openPopover() fireEvent.click(screen.getByText('Document 2')) @@ -306,7 +285,7 @@ describe('PreviewDocumentPicker', () => { it('should handle rapid toggle clicks', () => { renderComponent() - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') // Rapid clicks fireEvent.click(trigger) @@ -337,14 +316,14 @@ describe('PreviewDocumentPicker', () => { // Renders placeholder for missing name expect(screen.getByText('--')).toBeInTheDocument() // Portal wrapper renders - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle empty files array', () => { renderComponent({ files: [] }) // Component should render without crashing - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle very long document names', () => { @@ -374,7 +353,7 @@ describe('PreviewDocumentPicker', () => { render() // Component should render without crashing - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle large number of files', () => { @@ -382,7 +361,7 @@ describe('PreviewDocumentPicker', () => { renderComponent({ files: manyFiles }) // Component should accept large files array - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle files with same name but different extensions', () => { @@ -393,7 +372,7 @@ describe('PreviewDocumentPicker', () => { renderComponent({ files }) // Component should handle duplicate names - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -427,7 +406,7 @@ describe('PreviewDocumentPicker', () => { files: [createMockDocumentItem({ name: 'Single' })], }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle two files', () => { @@ -435,7 +414,7 @@ describe('PreviewDocumentPicker', () => { files: createMockDocumentList(2), }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) it('should handle many files', () => { @@ -443,7 +422,7 @@ describe('PreviewDocumentPicker', () => { files: createMockDocumentList(50), }) - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByTestId('popover')).toBeInTheDocument() }) }) @@ -451,23 +430,22 @@ describe('PreviewDocumentPicker', () => { it('should apply custom className', () => { renderComponent({ className: 'my-custom-class' }) - const trigger = screen.getByTestId('portal-trigger') - expect(trigger.querySelector('.my-custom-class')).toBeInTheDocument() + const trigger = screen.getByTestId('popover-trigger') + expect(trigger).toHaveClass('my-custom-class') }) it('should work without className', () => { renderComponent({ className: undefined }) - expect(screen.getByTestId('portal-trigger')).toBeInTheDocument() + expect(screen.getByTestId('popover-trigger')).toBeInTheDocument() }) it('should handle multiple class names', () => { renderComponent({ className: 'class-one class-two' }) - const trigger = screen.getByTestId('portal-trigger') - const element = trigger.querySelector('.class-one') - expect(element).toBeInTheDocument() - expect(element).toHaveClass('class-two') + const trigger = screen.getByTestId('popover-trigger') + expect(trigger).toHaveClass('class-one') + expect(trigger).toHaveClass('class-two') }) }) @@ -480,7 +458,7 @@ describe('PreviewDocumentPicker', () => { files: [], // Use empty files to avoid duplicate icons }) - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') expect(trigger.querySelector('svg')).toBeInTheDocument() }) }) @@ -491,6 +469,7 @@ describe('PreviewDocumentPicker', () => { it('should render all documents in the list', () => { const files = createMockDocumentList(5) renderComponent({ files }) + openPopover() // All documents should be visible files.forEach((file) => { @@ -503,6 +482,7 @@ describe('PreviewDocumentPicker', () => { const files = createMockDocumentList(3) renderComponent({ files, onChange }) + openPopover() fireEvent.click(screen.getByText('Document 1')) @@ -528,6 +508,7 @@ describe('PreviewDocumentPicker', () => { onChange={vi.fn()} />, ) + openPopover() expect(screen.getByText(/dataset\.preprocessDocument/)).toBeInTheDocument() }) }) @@ -537,9 +518,8 @@ describe('PreviewDocumentPicker', () => { it('should apply hover styles on trigger', () => { renderComponent() - const trigger = screen.getByTestId('portal-trigger') - const innerDiv = trigger.querySelector('.hover\\:bg-state-base-hover') - expect(innerDiv).toBeInTheDocument() + const trigger = screen.getByTestId('popover-trigger') + expect(trigger).toHaveClass('hover:bg-state-base-hover') }) it('should have truncate class for long names', () => { @@ -568,6 +548,7 @@ describe('PreviewDocumentPicker', () => { const files = createMockDocumentList(3) renderComponent({ files, onChange }) + openPopover() fireEvent.click(screen.getByText('Document 1')) @@ -582,10 +563,12 @@ describe('PreviewDocumentPicker', () => { ] renderComponent({ files: customFiles, onChange }) + openPopover() fireEvent.click(screen.getByText('Custom File 1')) expect(onChange).toHaveBeenCalledWith(customFiles[0]) + openPopover() fireEvent.click(screen.getByText('Custom File 2')) expect(onChange).toHaveBeenCalledWith(customFiles[1]) }) @@ -597,8 +580,11 @@ describe('PreviewDocumentPicker', () => { renderComponent({ files, onChange }) // Select multiple documents sequentially + openPopover() fireEvent.click(screen.getByText('Document 1')) + openPopover() fireEvent.click(screen.getByText('Document 3')) + openPopover() fireEvent.click(screen.getByText('Document 2')) expect(onChange).toHaveBeenCalledTimes(3) diff --git a/web/app/components/datasets/common/document-picker/index.tsx b/web/app/components/datasets/common/document-picker/index.tsx index d0e389255a..0566b590de 100644 --- a/web/app/components/datasets/common/document-picker/index.tsx +++ b/web/app/components/datasets/common/document-picker/index.tsx @@ -2,6 +2,11 @@ import type { FC } from 'react' import type { DocumentItem, ParentMode, SimpleDocumentDetail } from '@/models/datasets' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine } from '@remixicon/react' import { useBoolean } from 'ahooks' import * as React from 'react' @@ -9,11 +14,6 @@ import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { GeneralChunk, ParentChildChunk } from '@/app/components/base/icons/src/vender/knowledge' import Loading from '@/app/components/base/loading' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import SearchInput from '@/app/components/base/search-input' import { ChunkingMode } from '@/models/datasets' import { useDocumentList } from '@/service/knowledge/use-document' @@ -61,7 +61,6 @@ const DocumentPicker: FC = ({ const [open, { set: setOpen, - toggle: togglePopup, }] = useBoolean(false) const ArrowIcon = RiArrowDownSLine @@ -77,34 +76,40 @@ const DocumentPicker: FC = ({ }, [parentMode, t]) return ( - - -
- -
-
- - {' '} - {name || '--'} - - -
-
- - - {isGeneralMode && t('chunkingMode.general', { ns: 'dataset' })} - {isQAMode && t('chunkingMode.qa', { ns: 'dataset' })} - {isParentChild && `${t('chunkingMode.parentChild', { ns: 'dataset' })} · ${parentModeLabel}`} - + + +
+
+ + {' '} + {name || '--'} + + +
+
+ + + {isGeneralMode && t('chunkingMode.general', { ns: 'dataset' })} + {isQAMode && t('chunkingMode.qa', { ns: 'dataset' })} + {isParentChild && `${t('chunkingMode.parentChild', { ns: 'dataset' })} · ${parentModeLabel}`} + +
-
- - + )} + /> +
{documentsList @@ -125,9 +130,8 @@ const DocumentPicker: FC = ({
)}
- -
-
+ + ) } export default React.memo(DocumentPicker) diff --git a/web/app/components/datasets/common/document-picker/preview-document-picker.tsx b/web/app/components/datasets/common/document-picker/preview-document-picker.tsx index 03ee13b513..597ceda9a5 100644 --- a/web/app/components/datasets/common/document-picker/preview-document-picker.tsx +++ b/web/app/components/datasets/common/document-picker/preview-document-picker.tsx @@ -2,17 +2,17 @@ import type { FC } from 'react' import type { DocumentItem } from '@/models/datasets' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine } from '@remixicon/react' import { useBoolean } from 'ahooks' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import FileIcon from '../document-file-icon' import DocumentList from './document-list' @@ -35,7 +35,6 @@ const PreviewDocumentPicker: FC = ({ const [open, { set: setOpen, - toggle: togglePopup, }] = useBoolean(false) const ArrowIcon = RiArrowDownSLine @@ -45,27 +44,32 @@ const PreviewDocumentPicker: FC = ({ }, [onChange, setOpen]) return ( - - -
- -
-
- - {' '} - {name || '--'} - - + + +
+
+ + {' '} + {name || '--'} + + +
-
- - + )} + /> +
{files?.length > 1 &&
{t('preprocessDocument', { ns: 'dataset', num: files.length })}
} {files?.length > 0 @@ -81,9 +85,8 @@ const PreviewDocumentPicker: FC = ({
)}
- - -
+ + ) } export default React.memo(PreviewDocumentPicker) diff --git a/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/preview-panel.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/preview-panel.spec.tsx index 11f1286306..9c29206e7d 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/preview-panel.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/preview-panel.spec.tsx @@ -231,8 +231,9 @@ describe('StepTwoPreview', () => { describe('Props Passing', () => { it('should render preview button when isIdle is true', () => { render() - // ChunkPreview shows a preview button when idle - const previewButton = screen.queryByRole('button') + const previewButton = screen.getByRole('button', { + name: 'datasetPipeline.addDocuments.stepTwo.previewChunks', + }) expect(previewButton).toBeInTheDocument() }) @@ -240,13 +241,13 @@ describe('StepTwoPreview', () => { const onPreview = vi.fn() render() - // Find and click the preview button - const buttons = screen.getAllByRole('button') - const previewButton = buttons.find(btn => btn.textContent?.toLowerCase().includes('preview')) - if (previewButton) { - previewButton.click() - expect(onPreview).toHaveBeenCalled() - } + const previewButton = screen.getByRole('button', { + name: 'datasetPipeline.addDocuments.stepTwo.previewChunks', + }) + + previewButton.click() + + expect(onPreview).toHaveBeenCalled() }) }) diff --git a/web/app/components/datasets/settings/permission-selector/index.tsx b/web/app/components/datasets/settings/permission-selector/index.tsx index a9b0c348d8..724a239c94 100644 --- a/web/app/components/datasets/settings/permission-selector/index.tsx +++ b/web/app/components/datasets/settings/permission-selector/index.tsx @@ -1,17 +1,17 @@ import type { Member } from '@/models/common' import { Avatar } from '@langgenius/dify-ui/avatar' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine, RiGroup2Line, RiLock2Line } from '@remixicon/react' import { useDebounceFn } from 'ahooks' import * as React from 'react' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useSelector as useAppContextWithSelector } from '@/context/app-context' import { DatasetPermission } from '@/models/datasets' import MemberItem from './member-item' @@ -90,93 +90,98 @@ const PermissionSelector = ({ const selectedMemberNames = selectedMembers.map(member => member.name).join(', ') return ( - { + if (disabled) + return + setOpen(nextOpen) + }} >
- !disabled && setOpen(v => !v)} - className="block" - > -
- { - isOnlyMe && ( - <> -
- -
-
- {t('form.permissionsOnlyMe', { ns: 'datasetSettings' })} -
- - ) - } - { - isAllTeamMembers && ( - <> -
- -
-
- {t('form.permissionsAllMember', { ns: 'datasetSettings' })} -
- - ) - } - { - isPartialMembers && ( - <> -
- { - selectedMembers.length === 1 && ( - - ) - } - { - selectedMembers.length >= 2 && ( - <> + + { + isOnlyMe && ( + <> +
+ +
+
+ {t('form.permissionsOnlyMe', { ns: 'datasetSettings' })} +
+ + ) + } + { + isAllTeamMembers && ( + <> +
+ +
+
+ {t('form.permissionsAllMember', { ns: 'datasetSettings' })} +
+ + ) + } + { + isPartialMembers && ( + <> +
+ { + selectedMembers.length === 1 && ( - - - ) - } -
-
- {selectedMemberNames} -
- - ) - } - -
- - + ) + } + { + selectedMembers.length >= 2 && ( + <> + + + + ) + } +
+
+ {selectedMemberNames} +
+ + ) + } + +
+ )} + /> +
{/* Only me */} @@ -236,6 +241,7 @@ const PermissionSelector = ({ )} {filteredMemberList.map(member => ( } @@ -256,9 +262,9 @@ const PermissionSelector = ({
)}
- +
-
+ ) } diff --git a/web/app/components/header/account-setting/api-based-extension-page/__tests__/selector.spec.tsx b/web/app/components/header/account-setting/api-based-extension-page/__tests__/selector.spec.tsx index 82acd22d97..44dfdbc5a5 100644 --- a/web/app/components/header/account-setting/api-based-extension-page/__tests__/selector.spec.tsx +++ b/web/app/components/header/account-setting/api-based-extension-page/__tests__/selector.spec.tsx @@ -15,6 +15,8 @@ vi.mock('@/service/use-common', () => ({ useApiBasedExtensions: vi.fn(), })) +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) + describe('ApiBasedExtensionSelector', () => { const mockOnChange = vi.fn() const mockSetShowAccountSettingModal = vi.fn() diff --git a/web/app/components/header/account-setting/api-based-extension-page/selector.tsx b/web/app/components/header/account-setting/api-based-extension-page/selector.tsx index 041657d4be..3f207ef23f 100644 --- a/web/app/components/header/account-setting/api-based-extension-page/selector.tsx +++ b/web/app/components/header/account-setting/api-based-extension-page/selector.tsx @@ -1,4 +1,5 @@ import type { FC } from 'react' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { RiAddLine, RiArrowDownSLine, @@ -8,11 +9,6 @@ import { useTranslation } from 'react-i18next' import { ArrowUpRight, } from '@/app/components/base/icons/src/vender/line/arrows' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContext } from '@/context/modal-context' import { useApiBasedExtensions } from '@/service/use-common' @@ -41,35 +37,42 @@ const ApiBasedExtensionSelector: FC = ({ const currentItem = data?.find(item => item.id === value) return ( - - setOpen(v => !v)} className="w-full"> - { - currentItem - ? ( -
-
{currentItem.name}
-
-
- {currentItem.api_endpoint} + + { + currentItem + ? ( +
+
{currentItem.name}
+
+
+ {currentItem.api_endpoint} +
+ +
- -
-
- ) - : ( -
- {t('apiBasedExtension.selector.placeholder', { ns: 'common' })} - -
- ) - } - - + ) + : ( +
+ {t('apiBasedExtension.selector.placeholder', { ns: 'common' })} + +
+ ) + } + + )} + /> +
@@ -116,8 +119,8 @@ const ApiBasedExtensionSelector: FC = ({
-
- + + ) } diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/configure.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/configure.spec.tsx index 5f844d02e3..07344343f8 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/configure.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/configure.spec.tsx @@ -1,3 +1,4 @@ +import type { ButtonHTMLAttributes, ReactNode } from 'react' import type { DataSourceAuth } from '../types' import type { FormSchema } from '@/app/components/base/form/types' import type { AddApiKeyButtonProps, AddOAuthButtonProps, PluginPayload } from '@/app/components/plugins/plugin-auth/types' @@ -6,6 +7,15 @@ import { FormTypeEnum } from '@/app/components/base/form/types' import { AuthCategory } from '@/app/components/plugins/plugin-auth/types' import Configure from '../configure' +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) +vi.mock('@langgenius/dify-ui/button', () => ({ + Button: ({ children, ...props }: ButtonHTMLAttributes & { children?: ReactNode }) => ( + + ), +})) + /** * Configure Component Tests * Using Unit approach to ensure 100% coverage and stable tests. diff --git a/web/app/components/header/account-setting/data-source-page-new/configure.tsx b/web/app/components/header/account-setting/data-source-page-new/configure.tsx index 712fb91415..f242d17079 100644 --- a/web/app/components/header/account-setting/data-source-page-new/configure.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/configure.tsx @@ -5,6 +5,11 @@ import type { PluginPayload, } from '@/app/components/plugins/plugin-auth/types' import { Button } from '@langgenius/dify-ui/button' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiAddLine, } from '@remixicon/react' @@ -15,11 +20,6 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { AddApiKeyButton, AddOAuthButton, @@ -56,10 +56,6 @@ const Configure = ({ } }, [pluginPayload, t]) - const handleToggle = useCallback(() => { - setOpen(v => !v) - }, []) - const handleUpdate = useCallback(() => { setOpen(false) onUpdate?.() @@ -67,24 +63,26 @@ const Configure = ({ return ( <> - - - - - + + + {t('dataSource.configure', { ns: 'common' })} + + )} + /> +
{ !!canOAuth && ( @@ -122,8 +120,8 @@ const Configure = ({ ) }
-
-
+ + ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx index 43a27dac9b..d0ba140fde 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/add-custom-model.spec.tsx @@ -34,33 +34,17 @@ vi.mock('@remixicon/react', () => ({ RiAddLine: () =>
, })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ children, popupContent }: { children: React.ReactNode, popupContent: string }) => ( +vi.mock('@langgenius/dify-ui/tooltip', () => ({ + Tooltip: ({ children }: { children: React.ReactNode }) => (
{children} -
{popupContent}
), + TooltipTrigger: ({ render }: { render: React.ReactNode }) => <>{render}, + TooltipContent: ({ children }: { children: React.ReactNode }) =>
{children}
, })) -// Mock portal components to avoid async test DOM issues (consistent with sibling tests) -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open }: { children: React.ReactNode, open: boolean, onOpenChange: (open: boolean) => void }) => ( -
- {children} -
- ), - PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick: () => void }) => ( -
{children}
- ), - PortalToFollowElemContent: ({ children }: { children: React.ReactNode, open?: boolean }) => { - // In many tests, we need to find elements inside the content even if "closed" in state - // but not yet "removed" from DOM. However, to avoid multiple elements issues, - // we should be careful. - // For AddCustomModel, we need the content to be present when we click a model. - return
{children}
- }, -})) +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) describe('AddCustomModel', () => { const mockProvider = { @@ -94,7 +78,7 @@ describe('AddCustomModel', () => { />, ) - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByRole('button', { name: /modelProvider.addModel/i })) expect(mockHandleOpenModalForAddNewCustomModel).toHaveBeenCalled() }) @@ -107,10 +91,10 @@ describe('AddCustomModel', () => { />, ) - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByTestId('popover-trigger')) // The portal should be "open" - expect(screen.getByTestId('portal')).toHaveAttribute('data-open', 'true') + expect(screen.getByTestId('popover')).toHaveAttribute('data-open', 'true') expect(screen.getByText('gpt-4')).toBeInTheDocument() expect(screen.getByTestId('model-icon')).toBeInTheDocument() }) @@ -125,7 +109,7 @@ describe('AddCustomModel', () => { />, ) - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByTestId('popover-trigger')) fireEvent.click(screen.getByText('gpt-4')) expect(mockHandleOpenModalForAddCustomModelToModelList).toHaveBeenCalledWith(undefined, model) @@ -140,7 +124,7 @@ describe('AddCustomModel', () => { />, ) - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByTestId('popover-trigger')) fireEvent.click(screen.getByText(/modelProvider.auth.addNewModel/)) expect(mockHandleOpenModalForAddNewCustomModel).toHaveBeenCalled() @@ -159,7 +143,7 @@ describe('AddCustomModel', () => { expect(screen.getByTestId('tooltip-mock')).toBeInTheDocument() expect(screen.getByText('plugin.auth.credentialUnavailable')).toBeInTheDocument() - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByRole('button', { name: /modelProvider.addModel/i })) expect(mockHandleOpenModalForAddNewCustomModel).not.toHaveBeenCalled() }) }) diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx index a1e181e97e..5191357d83 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx @@ -7,6 +7,16 @@ import { Button, } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@langgenius/dify-ui/tooltip' import { RiAddCircleFill, RiAddLine, @@ -17,12 +27,6 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Tooltip from '@/app/components/base/tooltip' import { ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import ModelIcon from '../model-icon' import { useAuth } from './hooks/use-auth' @@ -67,12 +71,12 @@ const AddCustomModel = ({ }, ) const notAllowCustomCredential = provider.allow_custom_token === false - - const renderTrigger = useCallback((open?: boolean) => { - const Item = ( + const renderTrigger = useCallback((open?: boolean, onClick?: () => void) => { + const item = (
} + /> + - {renderTrigger(open)} - -
{ @@ -125,8 +123,8 @@ const AddCustomModel = ({ key={model.model} className="flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => { - handleOpenModalForAddCustomModelToModelList(undefined, model) setOpen(false) + handleOpenModalForAddCustomModelToModelList(undefined, model) }} > { - handleOpenModalForAddNewCustomModel() setOpen(false) + handleOpenModalForAddNewCustomModel() }} > @@ -160,8 +158,8 @@ const AddCustomModel = ({ ) }
- - + + ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx index a331181619..f405fb5528 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/index.spec.tsx @@ -45,6 +45,8 @@ vi.mock('../authorized-item', () => ({ ), })) +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) + describe('Authorized', () => { const mockProvider: ModelProvider = { provider: 'openai', diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx index ca0e0d9c73..d86eaf40c5 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/index.tsx @@ -1,3 +1,8 @@ +import type { + OffsetOptions, +} from '@floating-ui/react' +import type { Placement } from '@langgenius/dify-ui/popover' +import type { MouseEvent } from 'react' import type { ConfigurationMethodEnum, Credential, @@ -6,9 +11,6 @@ import type { ModelModalModeEnum, ModelProvider, } from '../../declarations' -import type { - PortalToFollowElemOptions, -} from '@/app/components/base/portal-to-follow-elem' import { AlertDialog, AlertDialogActions, @@ -19,6 +21,11 @@ import { } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiAddLine, } from '@remixicon/react' @@ -29,11 +36,6 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useAuth } from '../hooks' import AuthorizedItem from './authorized-item' @@ -43,7 +45,7 @@ type AuthorizedProps = { currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields authParams?: { isModelCredential?: boolean - onUpdate?: (newPayload?: any, formValues?: Record) => void + onUpdate?: (newPayload?: Record, formValues?: Record) => void onRemove?: (credentialId: string) => void mode?: ModelModalModeEnum } @@ -57,8 +59,8 @@ type AuthorizedProps = { renderTrigger: (open?: boolean) => React.ReactNode isOpen?: boolean onOpenChange?: (open: boolean) => void - offset?: PortalToFollowElemOptions['offset'] - placement?: PortalToFollowElemOptions['placement'] + offset?: number | OffsetOptions + placement?: Placement triggerPopupSameWidth?: boolean popupClassName?: string showItemSelectedIcon?: boolean @@ -132,9 +134,13 @@ const Authorized = ({ ) const handleEdit = useCallback((credential?: Credential, model?: CustomModel) => { - handleOpenModal(credential, model) setMergedIsOpen(false) + handleOpenModal(credential, model) }, [handleOpenModal, setMergedIsOpen]) + const handleDelete = useCallback((credential?: Credential, model?: CustomModel) => { + setMergedIsOpen(false) + openConfirmDelete(credential, model) + }, [openConfirmDelete, setMergedIsOpen]) const handleItemClick = useCallback((credential: Credential, model?: CustomModel) => { if (disableItemClick) @@ -148,30 +154,37 @@ const Authorized = ({ setMergedIsOpen(false) }, [handleActiveCredential, onItemClick, setMergedIsOpen, disableItemClick]) const notAllowCustomCredential = provider.allow_custom_token === false + const resolvedOffset = typeof offset === 'number' || typeof offset === 'function' ? undefined : offset + const sideOffset = typeof offset === 'number' ? offset : resolvedOffset?.mainAxis ?? 0 + const alignOffset = typeof offset === 'number' ? 0 : resolvedOffset?.crossAxis ?? resolvedOffset?.alignmentAxis ?? 0 + const popupProps = triggerPopupSameWidth + ? { style: { width: 'var(--anchor-width, auto)' } } + : undefined + const handleTriggerClick = useCallback((event: MouseEvent) => { + if (!triggerOnlyOpenModal) + return + + event.preventDefault() + handleOpenModal() + }, [handleOpenModal, triggerOnlyOpenModal]) return ( <> - - { - if (triggerOnlyOpenModal) { - handleOpenModal() - return - } - - setMergedIsOpen(!mergedIsOpen) - }} - asChild + {renderTrigger(mergedIsOpen)}
} + onClick={handleTriggerClick} + /> + - {renderTrigger(mergedIsOpen)} - -
{ - items.map((item, index) => ( - + items.map(item => ( + credential.credential_id).join('-')}> { - index !== items.length - 1 && ( + item !== items[items.length - 1] && (
) } @@ -245,8 +258,8 @@ const Authorized = ({ ) }
-
- +
+ !open && closeConfirmDelete()}>
diff --git a/web/app/components/plugins/marketplace/search-box/__tests__/index.spec.tsx b/web/app/components/plugins/marketplace/search-box/__tests__/index.spec.tsx index 8609ba5539..51b4087659 100644 --- a/web/app/components/plugins/marketplace/search-box/__tests__/index.spec.tsx +++ b/web/app/components/plugins/marketplace/search-box/__tests__/index.spec.tsx @@ -62,31 +62,38 @@ vi.mock('@/app/components/plugins/hooks', () => ({ }), })) -// Mock portal-to-follow-elem with shared open state +// Mock popover with shared open state let mockPortalOpenState = false +let mockPopoverOnOpenChange: ((open: boolean) => void) | undefined -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open }: { +vi.mock('@langgenius/dify-ui/popover', () => ({ + Popover: ({ children, open, onOpenChange }: { children: React.ReactNode open: boolean + onOpenChange?: (open: boolean) => void }) => { mockPortalOpenState = open + mockPopoverOnOpenChange = onOpenChange return (
{children}
) }, - PortalToFollowElemTrigger: ({ children, onClick, className }: { - children: React.ReactNode - onClick: () => void + PopoverTrigger: ({ children, render, className }: { + children?: React.ReactNode + render?: React.ReactNode className?: string }) => ( -
- {children} +
mockPopoverOnOpenChange?.(!mockPortalOpenState)} + className={className} + > + {render ?? children}
), - PortalToFollowElemContent: ({ children, className }: { + PopoverContent: ({ children, className }: { children: React.ReactNode className?: string }) => { diff --git a/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx b/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx index 117b8cdfab..e87022fe38 100644 --- a/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx +++ b/web/app/components/plugins/marketplace/search-box/__tests__/tags-filter.spec.tsx @@ -1,10 +1,19 @@ -import { fireEvent, render, screen } from '@testing-library/react' +import { + fireEvent, + render, + screen, + within, +} from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import TagsFilter from '../tags-filter' +const { mockTranslate } = vi.hoisted(() => ({ + mockTranslate: vi.fn((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), +})) + vi.mock('#i18n', () => ({ useTranslation: () => ({ - t: (key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key, + t: mockTranslate, }), })) @@ -46,20 +55,7 @@ vi.mock('@/app/components/base/input', () => ({ ), })) -vi.mock('@/app/components/base/portal-to-follow-elem', async () => { - const _React = await import('react') - return { - PortalToFollowElem: ({ children }: { children: React.ReactNode }) =>
{children}
, - PortalToFollowElemTrigger: ({ - children, - onClick, - }: { - children: React.ReactNode - onClick: () => void - }) => , - PortalToFollowElemContent: ({ children }: { children: React.ReactNode }) =>
{children}
, - } -}) +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) vi.mock('../trigger/marketplace', () => ({ default: ({ selectedTagsLength }: { selectedTagsLength: number }) => ( @@ -80,8 +76,16 @@ vi.mock('../trigger/tool-selector', () => ({ })) describe('TagsFilter', () => { + const ensurePopoverOpen = () => { + if (!screen.queryByTestId('popover-content')) + fireEvent.click(screen.getByTestId('popover-trigger')) + + return screen.getByTestId('popover-content') + } + beforeEach(() => { vi.clearAllMocks() + mockTranslate.mockImplementation((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key) }) it('renders marketplace trigger when used in marketplace', () => { @@ -100,6 +104,7 @@ describe('TagsFilter', () => { it('filters tag options by search text', () => { render() + fireEvent.click(screen.getByTestId('popover-trigger')) expect(screen.getByText('Agent')).toBeInTheDocument() expect(screen.getByText('RAG')).toBeInTheDocument() @@ -116,11 +121,20 @@ describe('TagsFilter', () => { const onTagsChange = vi.fn() const { rerender } = render() - fireEvent.click(screen.getByText('Agent')) + fireEvent.click(within(ensurePopoverOpen()).getByText('Agent')) expect(onTagsChange).toHaveBeenCalledWith([]) rerender() - fireEvent.click(screen.getByText('RAG')) + fireEvent.click(within(ensurePopoverOpen()).getByText('RAG')) expect(onTagsChange).toHaveBeenCalledWith(['agent', 'rag']) }) + + it('falls back to an empty placeholder when translation is missing', () => { + mockTranslate.mockImplementation(() => undefined as unknown as string) + + render() + fireEvent.click(screen.getByTestId('popover-trigger')) + + expect(screen.getByLabelText('tags-search')).toHaveAttribute('placeholder', '') + }) }) diff --git a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx index b078dbaa9b..d97420b672 100644 --- a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx +++ b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx @@ -1,14 +1,14 @@ 'use client' import { useTranslation } from '#i18n' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { useState } from 'react' import Checkbox from '@/app/components/base/checkbox' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useTags } from '@/app/components/plugins/hooks' import MarketplaceTrigger from './trigger/marketplace' import ToolSelectorTrigger from './trigger/tool-selector' @@ -37,43 +37,45 @@ const TagsFilter = ({ const selectedTagsLength = tags.length return ( - - setOpen(v => !v)} + + { + usedInMarketplace && ( + + ) + } + { + !usedInMarketplace && ( + + ) + } +
+ )} + /> + - { - usedInMarketplace && ( - - ) - } - { - !usedInMarketplace && ( - - ) - } - -
- - + + ) } diff --git a/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx index 01e195b21b..271252464a 100644 --- a/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-auth/authorized/__tests__/index.spec.tsx @@ -73,6 +73,8 @@ vi.mock('@/hooks/use-oauth', () => ({ openOAuthPopup: vi.fn(), })) +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) + // Mock service/use-triggers vi.mock('@/service/use-triggers', () => ({ useTriggerPluginDynamicOptions: () => ({ diff --git a/web/app/components/plugins/plugin-auth/authorized/index.tsx b/web/app/components/plugins/plugin-auth/authorized/index.tsx index fed2873b98..b8b34e33e0 100644 --- a/web/app/components/plugins/plugin-auth/authorized/index.tsx +++ b/web/app/components/plugins/plugin-auth/authorized/index.tsx @@ -1,7 +1,8 @@ -import type { Credential, PluginPayload } from '../types' import type { - PortalToFollowElemOptions, -} from '@/app/components/base/portal-to-follow-elem' + OffsetOptions, +} from '@floating-ui/react' +import type { Placement } from '@langgenius/dify-ui/popover' +import type { Credential, PluginPayload } from '../types' import { AlertDialog, AlertDialogActions, @@ -12,6 +13,11 @@ import { } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { toast } from '@langgenius/dify-ui/toast' import { RiArrowDownSLine, @@ -23,11 +29,6 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import Indicator from '@/app/components/header/indicator' import Authorize from '../authorize' import ApiKeyModal from '../authorize/api-key-modal' @@ -48,8 +49,8 @@ type AuthorizedProps = { renderTrigger?: (open?: boolean) => React.ReactNode isOpen?: boolean onOpenChange?: (open: boolean) => void - offset?: PortalToFollowElemOptions['offset'] - placement?: PortalToFollowElemOptions['placement'] + offset?: number | OffsetOptions + placement?: Placement triggerPopupSameWidth?: boolean popupClassName?: string disableSetDefault?: boolean @@ -96,11 +97,12 @@ const Authorized = ({ const [deleteCredentialId, setDeleteCredentialId] = useState(null) const { mutateAsync: deletePluginCredential } = useDeletePluginCredentialHook(pluginPayload) const openConfirm = useCallback((credentialId?: string) => { + setMergedIsOpen(false) if (credentialId) pendingOperationCredentialId.current = credentialId setDeleteCredentialId(pendingOperationCredentialId.current) - }, []) + }, [setMergedIsOpen]) const closeConfirm = useCallback(() => { setDeleteCredentialId(null) pendingOperationCredentialId.current = null @@ -130,11 +132,12 @@ const Authorized = ({ handleSetDoingAction(false) } }, [deletePluginCredential, onUpdate, t, handleSetDoingAction]) - const [editValues, setEditValues] = useState | null>(null) - const handleEdit = useCallback((id: string, values: Record) => { + const [editValues, setEditValues] = useState | null>(null) + const handleEdit = useCallback((id: string, values: Record) => { + setMergedIsOpen(false) pendingOperationCredentialId.current = id setEditValues(values) - }, []) + }, [setMergedIsOpen]) const handleRemove = useCallback(() => { setDeleteCredentialId(pendingOperationCredentialId.current) }, []) @@ -171,49 +174,59 @@ const Authorized = ({ }, [updatePluginCredential, t, handleSetDoingAction, onUpdate]) const unavailableCredentials = credentials.filter(credential => credential.not_allowed_to_use) const unavailableCredential = credentials.find(credential => credential.not_allowed_to_use && credential.is_default) + const resolvedOffset = typeof offset === 'number' || typeof offset === 'function' ? undefined : offset + const sideOffset = typeof offset === 'number' ? offset : resolvedOffset?.mainAxis ?? 0 + const alignOffset = typeof offset === 'number' ? 0 : resolvedOffset?.crossAxis ?? resolvedOffset?.alignmentAxis ?? 0 + const popupProps = triggerPopupSameWidth + ? { style: { width: 'var(--anchor-width, auto)' } } + : undefined return ( <> - - setMergedIsOpen(!mergedIsOpen)} - asChild - > - { - renderTrigger - ? renderTrigger(mergedIsOpen) - : ( - - ) - } - - + { + credentials.length > 1 + ? t('auth.authorizations', { ns: 'plugin' }) + : t('auth.authorization', { ns: 'plugin' }) + } + { + !!unavailableCredentials.length && ( + ` (${unavailableCredentials.length} ${t('auth.unavailable', { ns: 'plugin' })})` + ) + } + + + ) + } +
+ )} + /> +
- - + + !open && closeConfirm()}>
diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/app-picker.spec.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/app-picker.spec.tsx index a319d2f8c4..af3f97c889 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/app-picker.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/app-picker.spec.tsx @@ -46,8 +46,8 @@ vi.mock('@/app/components/base/input', () => ({ ), })) -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ +vi.mock('@langgenius/dify-ui/popover', () => ({ + Popover: ({ children, open, }: { @@ -58,18 +58,20 @@ vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ {children}
), - PortalToFollowElemTrigger: ({ + PopoverTrigger: ({ children, + render, onClick, }: { children: ReactNode + render?: ReactNode onClick?: () => void }) => ( ), - PortalToFollowElemContent: ({ children }: { children: ReactNode }) => ( + PopoverContent: ({ children }: { children: ReactNode }) => (
{children}
), })) diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/index.spec.tsx index f7dd1921e4..7bc23d0223 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/__tests__/index.spec.tsx @@ -76,7 +76,7 @@ afterAll(() => { // Mock portal components for controlled positioning in tests // Use React context to properly scope open state per portal instance (for nested portals) -vi.mock('@/app/components/base/portal-to-follow-elem', () => { +vi.mock('@langgenius/dify-ui/popover', () => { // Context reference shared across mock components let sharedContext: React.Context | null = null @@ -90,7 +90,7 @@ vi.mock('@/app/components/base/portal-to-follow-elem', () => { } return { - PortalToFollowElem: ({ + Popover: ({ children, open, }: { @@ -104,20 +104,22 @@ vi.mock('@/app/components/base/portal-to-follow-elem', () => { React.createElement('div', { 'data-testid': 'portal-to-follow-elem', 'data-open': open }, children), ) }, - PortalToFollowElemTrigger: ({ + PopoverTrigger: ({ children, + render, onClick, className, }: { children: ReactNode + render?: ReactNode onClick?: () => void className?: string }) => (
- {children} + {render ?? children}
), - PortalToFollowElemContent: ({ children, className }: { children: ReactNode, className?: string }) => { + PopoverContent: ({ children, className }: { children: ReactNode, className?: string }) => { const Context = getContext() const isOpen = React.useContext(Context) if (!isOpen) diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx index 41140ac63b..cf387b1715 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx @@ -5,16 +5,16 @@ import type { } from '@floating-ui/react' import type { FC } from 'react' import type { App } from '@/types/app' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import * as React from 'react' import { useCallback, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { AppModeEnum } from '@/types/app' type Props = { @@ -154,26 +154,33 @@ const AppPicker: FC = ({ } } - const handleTriggerClick = () => { - if (disabled) + const resolvedOffset = typeof offset === 'number' || typeof offset === 'function' ? undefined : offset + const sideOffset = typeof offset === 'number' ? offset : resolvedOffset?.mainAxis ?? 0 + const alignOffset = typeof offset === 'number' ? 0 : resolvedOffset?.crossAxis ?? resolvedOffset?.alignmentAxis ?? 0 + const handleTriggerClick = useCallback((event: React.MouseEvent) => { + event.preventDefault() + if (disabled || isShow) return + onShowChange(true) - } + }, [disabled, isShow, onShowChange]) return ( - - {trigger}
} onClick={handleTriggerClick} - > - {trigger} - + /> - +
= ({
-
- +
+ ) } diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx index 97e144af6f..76dbdba7aa 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx @@ -5,14 +5,14 @@ import type { } from '@floating-ui/react' import type { FC } from 'react' import type { App } from '@/types/app' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import * as React from 'react' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import AppInputsPanel from '@/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel' import AppPicker from '@/app/components/plugins/plugin-detail-panel/app-selector/app-picker' import AppTrigger from '@/app/components/plugins/plugin-detail-panel/app-selector/app-trigger' @@ -94,6 +94,9 @@ const AppSelector: FC = ({ }, [currentAppInfo, displayedApps]) const hasMore = hasNextPage ?? true + const resolvedOffset = typeof offset === 'number' || typeof offset === 'function' ? undefined : offset + const sideOffset = typeof offset === 'number' ? offset : resolvedOffset?.mainAxis ?? 0 + const alignOffset = typeof offset === 'number' ? 0 : resolvedOffset?.crossAxis ?? resolvedOffset?.alignmentAxis ?? 0 const handleLoadMore = useCallback(async () => { if (isFetchingNextPage || !hasMore) @@ -102,11 +105,13 @@ const AppSelector: FC = ({ await fetchNextPage() }, [fetchNextPage, hasMore, isFetchingNextPage]) - const handleTriggerClick = () => { - if (disabled) + const handleTriggerClick = useCallback((event: React.MouseEvent) => { + event.preventDefault() + if (disabled || isShow) return + setIsShow(true) - } + }, [disabled, isShow]) const [isShowChooseApp, setIsShowChooseApp] = useState(false) const handleSelectApp = (app: App) => { @@ -143,22 +148,27 @@ const AppSelector: FC = ({ return ( <> - - + +
+ )} onClick={handleTriggerClick} + /> + - -
-
{t('appSelector.label', { ns: 'app' })}
@@ -193,8 +203,8 @@ const AppSelector: FC = ({ /> )}
- - + + ) } diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx index 168e4f1eba..acd9a9b146 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx @@ -153,8 +153,8 @@ vi.mock('@/app/components/plugins/plugin-auth', () => ({ })) // Portal components need mocking for controlled positioning in tests -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ +vi.mock('@langgenius/dify-ui/popover', () => ({ + Popover: ({ children, open, }: { @@ -165,18 +165,20 @@ vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ {children}
), - PortalToFollowElemTrigger: ({ + PopoverTrigger: ({ children, + render, onClick, }: { children: ReactNode + render?: ReactNode onClick?: () => void }) => (
- {children} + {render ?? children}
), - PortalToFollowElemContent: ({ children }: { children: ReactNode }) => ( + PopoverContent: ({ children }: { children: ReactNode }) => (
{children}
), })) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index b5e69ce254..109b0ece4c 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -8,13 +8,13 @@ import type { Node } from 'reactflow' import type { ToolValue } from '@/app/components/workflow/block-selector/types' import type { NodeOutPutVar } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import * as React from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { CollectionType } from '@/app/components/tools/types' import Link from '@/next/link' import { @@ -102,15 +102,21 @@ const ToolSelector: FC = ({ getSettingsValue, } = state - const handleTriggerClick = () => { + const handleTriggerClick = (event: React.MouseEvent) => { + event.preventDefault() if (disabled) return + if (!currentProvider || !currentTool) + return setIsShow(true) } // Determine portal open state based on controlled vs uncontrolled mode const portalOpen = trigger ? controlledState : isShow const onPortalOpenChange = trigger ? onControlledStateChange : setIsShow + const resolvedOffset = typeof offset === 'number' || typeof offset === 'function' ? undefined : offset + const sideOffset = typeof offset === 'number' ? offset : resolvedOffset?.mainAxis ?? 0 + const alignOffset = typeof offset === 'number' ? 0 : resolvedOffset?.crossAxis ?? resolvedOffset?.alignmentAxis ?? 0 // Build error tooltip content const renderErrorTip = () => ( @@ -134,57 +140,58 @@ const ToolSelector: FC = ({ ) return ( - - { - if (!currentProvider || !currentTool) - return - handleTriggerClick() - }} + + {trigger} + + {/* Default trigger - no value */} + {!trigger && !value?.provider_name && ( + + )} + + {/* Default trigger - with value */} + {!trigger && value?.provider_name && ( + + )} + + )} + onClick={handleTriggerClick} + /> + + - {trigger} - - {/* Default trigger - no value */} - {!trigger && !value?.provider_name && ( - - )} - - {/* Default trigger - with value */} - {!trigger && value?.provider_name && ( - - )} - - -
= ({ onParamsFormChange={handleParamsFormChange} />
-
-
+ + ) } diff --git a/web/app/components/plugins/plugin-page/filter-management/__tests__/category-filter.spec.tsx b/web/app/components/plugins/plugin-page/filter-management/__tests__/category-filter.spec.tsx index e4b698a5f8..c04012c498 100644 --- a/web/app/components/plugins/plugin-page/filter-management/__tests__/category-filter.spec.tsx +++ b/web/app/components/plugins/plugin-page/filter-management/__tests__/category-filter.spec.tsx @@ -2,17 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open }: { children: React.ReactNode, open: boolean }) => ( -
{children}
- ), - PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick: () => void }) => ( -
{children}
- ), - PortalToFollowElemContent: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), -})) +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) vi.mock('@langgenius/dify-ui/cn', () => ({ cn: (...args: unknown[]) => args.filter(Boolean).join(' '), @@ -67,7 +57,7 @@ describe('CategoriesFilter', () => { const mockOnChange = vi.fn() render() - const trigger = screen.getByTestId('portal-trigger') + const trigger = screen.getByTestId('popover-trigger') const clearSvg = trigger.querySelector('svg') fireEvent.click(clearSvg!) expect(mockOnChange).toHaveBeenCalledWith([]) @@ -75,6 +65,7 @@ describe('CategoriesFilter', () => { it('should render category options in dropdown', () => { render() + fireEvent.click(screen.getByTestId('popover-trigger')) expect(screen.getByText('Tool'))!.toBeInTheDocument() expect(screen.getByText('Model'))!.toBeInTheDocument() @@ -85,6 +76,7 @@ describe('CategoriesFilter', () => { const mockOnChange = vi.fn() render() + fireEvent.click(screen.getByTestId('popover-trigger')) fireEvent.click(screen.getByText('Tool')) expect(mockOnChange).toHaveBeenCalledWith(['tool']) }) @@ -93,8 +85,20 @@ describe('CategoriesFilter', () => { const mockOnChange = vi.fn() render() + fireEvent.click(screen.getByTestId('popover-trigger')) const toolElements = screen.getAllByText('Tool') fireEvent.click(toolElements[toolElements.length - 1]!) expect(mockOnChange).toHaveBeenCalledWith([]) }) + + it('should filter categories by search text', () => { + render() + + fireEvent.click(screen.getByTestId('popover-trigger')) + fireEvent.change(screen.getByPlaceholderText('plugin.searchCategories'), { target: { value: 'mod' } }) + + expect(screen.queryByText('Tool')).not.toBeInTheDocument() + expect(screen.getByText('Model')).toBeInTheDocument() + expect(screen.queryByText('Extension')).not.toBeInTheDocument() + }) }) diff --git a/web/app/components/plugins/plugin-page/filter-management/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/filter-management/__tests__/index.spec.tsx index f30b5fb5fa..46493a87df 100644 --- a/web/app/components/plugins/plugin-page/filter-management/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/filter-management/__tests__/index.spec.tsx @@ -1,6 +1,7 @@ import type { Category, Tag } from '../constant' import type { FilterState } from '../index' import { act, fireEvent, render, renderHook, screen, waitFor } from '@testing-library/react' +import { createContext, useContext } from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' // ==================== Imports (after mocks) ==================== @@ -68,19 +69,47 @@ vi.mock('../../../hooks', () => ({ }), })) -// Track portal open state for testing -let mockPortalOpenState = false +type MockPopoverContextValue = { + open: boolean + onOpenChange?: (open: boolean) => void +} -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open }: { children: React.ReactNode, open: boolean }) => { - mockPortalOpenState = open - return
{children}
- }, - PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick: () => void }) => ( -
{children}
+const MockPopoverContext = createContext({ + open: false, +}) + +vi.mock('@langgenius/dify-ui/popover', () => ({ + Popover: ({ children, open, onOpenChange }: { + children: React.ReactNode + open: boolean + onOpenChange?: (open: boolean) => void + }) => ( + +
{children}
+
), - PortalToFollowElemContent: ({ children, className }: { children: React.ReactNode, className?: string }) => { - if (!mockPortalOpenState) + PopoverTrigger: ({ children, render, className }: { + children?: React.ReactNode + render?: React.ReactNode + className?: string + }) => { + const { open, onOpenChange } = useContext(MockPopoverContext) + return ( +
onOpenChange?.(!open)} + className={className} + > + {render ?? children} +
+ ) + }, + PopoverContent: ({ children, className }: { + children: React.ReactNode + className?: string + }) => { + const { open } = useContext(MockPopoverContext) + if (!open) return null return
{children}
}, @@ -457,7 +486,6 @@ describe('SearchBox Component', () => { describe('CategoriesFilter Component', () => { beforeEach(() => { vi.clearAllMocks() - mockPortalOpenState = false }) describe('Rendering', () => { @@ -694,7 +722,6 @@ describe('CategoriesFilter Component', () => { describe('TagFilter Component', () => { beforeEach(() => { vi.clearAllMocks() - mockPortalOpenState = false }) describe('Rendering', () => { @@ -857,7 +884,6 @@ describe('FilterManagement Component', () => { beforeEach(() => { vi.clearAllMocks() mockInitFilters = createFilterState() - mockPortalOpenState = false }) describe('Rendering', () => { diff --git a/web/app/components/plugins/plugin-page/filter-management/__tests__/tag-filter.spec.tsx b/web/app/components/plugins/plugin-page/filter-management/__tests__/tag-filter.spec.tsx index ff3cd3d97c..f5db25bf5a 100644 --- a/web/app/components/plugins/plugin-page/filter-management/__tests__/tag-filter.spec.tsx +++ b/web/app/components/plugins/plugin-page/filter-management/__tests__/tag-filter.spec.tsx @@ -2,8 +2,6 @@ import { fireEvent, render, screen, within } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import TagFilter from '../tag-filter' -let portalOpen = false - vi.mock('../../../hooks', () => ({ useTags: () => ({ tags: [ @@ -19,35 +17,17 @@ vi.mock('../../../hooks', () => ({ }), })) -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ - children, - open, - }: { - children: React.ReactNode - open: boolean - }) => { - portalOpen = open - return
{children}
- }, - PortalToFollowElemTrigger: ({ - children, - onClick, - }: { - children: React.ReactNode - onClick: () => void - }) => , - PortalToFollowElemContent: ({ - children, - }: { - children: React.ReactNode - }) => portalOpen ?
{children}
: null, -})) +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) describe('TagFilter', () => { beforeEach(() => { vi.clearAllMocks() - portalOpen = false + }) + + it('renders the all tags placeholder when nothing is selected', () => { + render() + + expect(screen.getByText('pluginTags.allTags')).toBeInTheDocument() }) it('renders selected tag labels and the overflow counter', () => { @@ -61,8 +41,8 @@ describe('TagFilter', () => { const onChange = vi.fn() render() - fireEvent.click(screen.getByTestId('trigger')) - const portal = screen.getByTestId('portal-content') + fireEvent.click(screen.getByTestId('popover-trigger')) + const portal = screen.getByTestId('popover-content') fireEvent.change(screen.getByPlaceholderText('pluginTags.searchTags'), { target: { value: 'ra' } }) @@ -73,4 +53,24 @@ describe('TagFilter', () => { expect(onChange).toHaveBeenCalledWith(['agent', 'rag']) }) + + it('clears all selected tags when the clear icon is clicked', () => { + const onChange = vi.fn() + render() + + const trigger = screen.getByTestId('popover-trigger') + fireEvent.click(trigger.querySelector('svg')!) + + expect(onChange).toHaveBeenCalledWith([]) + }) + + it('removes a selected tag when clicking the same option again', () => { + const onChange = vi.fn() + render() + + fireEvent.click(screen.getByTestId('popover-trigger')) + fireEvent.click(within(screen.getByTestId('popover-content')).getByText('Agent')) + + expect(onChange).toHaveBeenCalledWith([]) + }) }) diff --git a/web/app/components/plugins/plugin-page/filter-management/category-filter.tsx b/web/app/components/plugins/plugin-page/filter-management/category-filter.tsx index 8dbef5395d..f75c63be94 100644 --- a/web/app/components/plugins/plugin-page/filter-management/category-filter.tsx +++ b/web/app/components/plugins/plugin-page/filter-management/category-filter.tsx @@ -1,6 +1,11 @@ 'use client' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine, RiCloseCircleFill, @@ -9,11 +14,6 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useCategories } from '../../hooks' type CategoriesFilterProps = { @@ -38,61 +38,64 @@ const CategoriesFilter = ({ const selectedTagsLength = value.length return ( - - setOpen(v => !v)}> -
+ +
+ { + !selectedTagsLength && t('allCategories', { ns: 'plugin' }) + } + { + !!selectedTagsLength && value.map(val => categoriesMap[val]!.label).slice(0, 2).join(',') + } + { + selectedTagsLength > 2 && ( +
+ + + {selectedTagsLength - 2} +
+ ) + } +
{ - !selectedTagsLength && t('allCategories', { ns: 'plugin' }) + !!selectedTagsLength && ( + { + e.stopPropagation() + onChange([]) + } + } + /> + ) } { - !!selectedTagsLength && value.map(val => categoriesMap[val]!.label).slice(0, 2).join(',') - } - { - selectedTagsLength > 2 && ( -
- + - {selectedTagsLength - 2} -
+ !selectedTagsLength && ( + ) }
- { - !!selectedTagsLength && ( - { - e.stopPropagation() - onChange([]) - } - } - /> - ) - } - { - !selectedTagsLength && ( - - ) - } - -
- + )} + /> +
- - + + ) } diff --git a/web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx b/web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx index e245895b3b..6916edd219 100644 --- a/web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx +++ b/web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx @@ -1,6 +1,11 @@ 'use client' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine, RiCloseCircleFill, @@ -9,11 +14,6 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useTags } from '../../hooks' type TagsFilterProps = { @@ -38,56 +38,62 @@ const TagsFilter = ({ const selectedTagsLength = value.length return ( - - setOpen(v => !v)}> -
+ +
+ { + !selectedTagsLength && t('allTags', { ns: 'pluginTags' }) + } + { + !!selectedTagsLength && value.map(val => getTagLabel(val)).slice(0, 2).join(',') + } + { + selectedTagsLength > 2 && ( +
+ + + {selectedTagsLength - 2} +
+ ) + } +
{ - !selectedTagsLength && t('allTags', { ns: 'pluginTags' }) + !!selectedTagsLength && ( + { + e.stopPropagation() + onChange([]) + }} + /> + ) } { - !!selectedTagsLength && value.map(val => getTagLabel(val)).slice(0, 2).join(',') - } - { - selectedTagsLength > 2 && ( -
- + - {selectedTagsLength - 2} -
+ !selectedTagsLength && ( + ) }
- { - !!selectedTagsLength && ( - onChange([])} - /> - ) - } - { - !selectedTagsLength && ( - - ) - } -
- - + )} + /> +
- - + + ) } diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/__tests__/index.spec.tsx index dda765b48f..cff2a5f4c2 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/__tests__/index.spec.tsx @@ -192,27 +192,6 @@ vi.mock('ahooks', () => ({ useKeyPress: vi.fn(), })) -let portalOpenState = false -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open, onOpenChange: _onOpenChange }: PropsWithChildren<{ - open: boolean - onOpenChange: (open: boolean) => void - placement?: string - offset?: unknown - }>) => { - portalOpenState = open - return
{children}
- }, - PortalToFollowElemTrigger: ({ children, onClick }: PropsWithChildren<{ onClick?: () => void }>) => ( -
{children}
- ), - PortalToFollowElemContent: ({ children }: PropsWithChildren) => { - if (!portalOpenState) - return null - return
{children}
- }, -})) - vi.mock('../../../publish-as-knowledge-pipeline-modal', () => ({ default: ({ onConfirm, onCancel }: { onConfirm: (name: string, icon: unknown, description?: string) => void @@ -229,7 +208,6 @@ vi.mock('../../../publish-as-knowledge-pipeline-modal', () => ({ describe('RagPipelineHeader', () => { beforeEach(() => { vi.clearAllMocks() - portalOpenState = false mockStoreState = { pipelineId: 'test-pipeline-id', showDebugAndPreviewPanel: false, @@ -351,7 +329,6 @@ describe('InputFieldButton', () => { describe('Publisher', () => { beforeEach(() => { vi.clearAllMocks() - portalOpenState = false }) describe('Rendering', () => { @@ -367,9 +344,9 @@ describe('Publisher', () => { expect(button)!.toHaveClass('px-2') }) - it('should render portal trigger element', () => { + it('should render publish trigger button', () => { render() - expect(screen.getByTestId('portal-trigger'))!.toBeInTheDocument() + expect(screen.getByRole('button', { name: /workflow\.common\.publish/i }))!.toBeInTheDocument() }) }) @@ -377,7 +354,7 @@ describe('Publisher', () => { it('should call handleSyncWorkflowDraft when opening', () => { render() - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByRole('button', { name: /workflow\.common\.publish/i })) expect(mockHandleSyncWorkflowDraft).toHaveBeenCalledWith(true) }) @@ -385,12 +362,14 @@ describe('Publisher', () => { it('should toggle open state when trigger clicked', () => { render() - const portal = screen.getByTestId('portal-elem') - expect(portal)!.toHaveAttribute('data-open', 'false') + const trigger = screen.getByRole('button', { name: /workflow\.common\.publish/i }) + expect(trigger)!.toHaveAttribute('aria-expanded', 'false') - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(trigger) expect(mockHandleSyncWorkflowDraft).toHaveBeenCalled() + expect(trigger)!.toHaveAttribute('aria-expanded', 'true') + expect(screen.getByText(/workflow\.common\.publishUpdate/i))!.toBeInTheDocument() }) }) }) @@ -978,7 +957,6 @@ describe('RunMode', () => { describe('Integration', () => { beforeEach(() => { vi.clearAllMocks() - portalOpenState = false mockStoreState = { pipelineId: 'test-pipeline-id', showDebugAndPreviewPanel: false, diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx index abb334b393..afd7c04ed1 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx @@ -6,6 +6,40 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import Publisher from '../index' import Popup from '../popup' +vi.mock('@langgenius/dify-ui/popover', async () => await import('@/__mocks__/base-ui-popover')) +vi.mock('@langgenius/dify-ui/button', () => ({ + Button: ({ children, onClick, disabled, variant, className }: Record) => ( + + ), +})) +vi.mock('@langgenius/dify-ui/alert-dialog', () => ({ + AlertDialog: ({ children, open, onOpenChange }: { children: React.ReactNode, open?: boolean, onOpenChange?: (open: boolean) => void }) => ( + open + ? ( +
+ {children} + +
+ ) + : null + ), + AlertDialogActions: ({ children }: { children: React.ReactNode }) =>
{children}
, + AlertDialogCancelButton: ({ children }: { children: React.ReactNode }) => , + AlertDialogConfirmButton: ({ children, onClick, disabled }: Record) => , + AlertDialogContent: ({ children }: { children: React.ReactNode }) =>
{children}
, + AlertDialogDescription: ({ children }: { children: React.ReactNode }) =>
{children}
, + AlertDialogTitle: ({ children }: { children: React.ReactNode }) =>
{children}
, +})) + const mockPush = vi.fn() vi.mock('@/next/navigation', () => ({ useParams: () => ({ datasetId: 'test-dataset-id' }), @@ -60,7 +94,8 @@ vi.mock('@/context/dataset-detail', () => ({ const mockSetShowPricingModal = vi.fn() vi.mock('@/context/modal-context', () => ({ - useModalContextSelector: () => mockSetShowPricingModal, + useModalContextSelector: (selector: (state: { setShowPricingModal: typeof mockSetShowPricingModal }) => T): T => + selector({ setShowPricingModal: mockSetShowPricingModal }), })) const mockIsAllowPublishAsCustomKnowledgePipelineTemplate = vi.fn(() => true) @@ -200,8 +235,7 @@ describe('publisher', () => { it('should render portal element in closed state by default', () => { renderWithQueryClient() - const trigger = screen.getByText('workflow.common.publish').closest('[data-state]') - expect(trigger).toHaveAttribute('data-state', 'closed') + expect(screen.getByTestId('popover')).toHaveAttribute('data-open', 'false') expect(screen.queryByText('workflow.common.publishUpdate')).not.toBeInTheDocument() }) @@ -277,6 +311,25 @@ describe('publisher', () => { expect(screen.getByText('workflow.common.publishUpdate')).toBeInTheDocument() }) }) + + it('should close the outer popover before opening publish-as follow-up flow', async () => { + mockPublishedAt.mockReturnValue(1700000000) + mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(false) + renderWithQueryClient() + + fireEvent.click(screen.getByText('workflow.common.publish')) + + await waitFor(() => { + expect(screen.getByText('pipeline.common.publishAs')).toBeInTheDocument() + }) + + fireEvent.click(screen.getByText('pipeline.common.publishAs')) + + await waitFor(() => { + expect(screen.queryByText('pipeline.common.publishAs')).not.toBeInTheDocument() + }) + expect(mockSetShowPricingModal).toHaveBeenCalled() + }) }) }) @@ -688,7 +741,7 @@ describe('publisher', () => { expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument() }) - fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' })) + fireEvent.click(screen.getByTestId('alert-dialog-close')) await waitFor(() => { expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx index 103ee53210..dab8046c43 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/popup.spec.tsx @@ -3,6 +3,31 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import Popup from '../popup' +vi.mock('@langgenius/dify-ui/alert-dialog', () => ({ + AlertDialog: ({ children, open, onOpenChange }: { children: React.ReactNode, open?: boolean, onOpenChange?: (open: boolean) => void }) => ( + open + ? ( +
+ {children} + +
+ ) + : null + ), + AlertDialogActions: ({ children }: { children?: React.ReactNode }) =>
{children}
, + AlertDialogCancelButton: ({ children }: { children?: React.ReactNode }) => , + AlertDialogConfirmButton: ({ children, onClick, disabled }: { + children?: React.ReactNode + onClick?: () => void + disabled?: boolean + }) => , + AlertDialogContent: ({ children }: { children?: React.ReactNode }) =>
{children}
, + AlertDialogDescription: ({ children }: { children?: React.ReactNode }) =>
{children}
, + AlertDialogTitle: ({ children }: { children?: React.ReactNode }) =>
{children}
, +})) + const mockPublishWorkflow = vi.fn().mockResolvedValue({ created_at: '2024-01-01T00:00:00Z' }) const mockPublishAsCustomizedPipeline = vi.fn().mockResolvedValue({}) const toastMocks = vi.hoisted(() => ({ @@ -36,6 +61,8 @@ let mockPublishedAt: string | undefined = '2024-01-01T00:00:00Z' let mockDraftUpdatedAt: string | undefined = '2024-06-01T00:00:00Z' let mockPipelineId: string | undefined = 'pipeline-123' let mockIsAllowPublishAsCustom = true +const mockUseBoolean = vi.hoisted(() => vi.fn()) +const mockUseKeyPress = vi.hoisted(() => vi.fn()) vi.mock('@/next/navigation', () => ({ useParams: () => ({ datasetId: 'ds-123' }), useRouter: () => ({ push: mockPush }), @@ -48,14 +75,8 @@ vi.mock('@/next/link', () => ({ })) vi.mock('ahooks', () => ({ - useBoolean: (initial: boolean) => { - const state = { value: initial } - return [state.value, { - setFalse: vi.fn(), - setTrue: vi.fn(), - }] - }, - useKeyPress: vi.fn(), + useBoolean: (initial: boolean) => mockUseBoolean(initial), + useKeyPress: (...args: unknown[]) => mockUseKeyPress(...args), })) vi.mock('@/app/components/workflow/store', () => ({ @@ -126,7 +147,8 @@ vi.mock('@/context/i18n', () => ({ })) vi.mock('@/context/modal-context', () => ({ - useModalContextSelector: () => mockSetShowPricingModal, + useModalContextSelector: (selector: (state: { setShowPricingModal: typeof mockSetShowPricingModal }) => T) => + selector({ setShowPricingModal: mockSetShowPricingModal }), })) vi.mock('@/context/provider-context', () => ({ @@ -194,6 +216,11 @@ describe('Popup', () => { mockDraftUpdatedAt = '2024-06-01T00:00:00Z' mockPipelineId = 'pipeline-123' mockIsAllowPublishAsCustom = true + mockUseBoolean.mockImplementation((initial: boolean) => [initial, { + setFalse: vi.fn(), + setTrue: vi.fn(), + }]) + mockUseKeyPress.mockImplementation(() => {}) }) afterEach(() => { @@ -289,12 +316,61 @@ describe('Popup', () => { describe('Publish As Knowledge Pipeline', () => { it('should show pricing modal when not allowed', () => { mockIsAllowPublishAsCustom = false - render() + const onRequestClose = vi.fn() + render() fireEvent.click(screen.getByText('pipeline.common.publishAs')) + expect(onRequestClose).toHaveBeenCalledTimes(1) expect(mockSetShowPricingModal).toHaveBeenCalled() }) + + it('should request closing the outer popover before opening publish-as modal', () => { + const onRequestClose = vi.fn() + render() + + fireEvent.click(screen.getByText('pipeline.common.publishAs')) + + expect(onRequestClose).toHaveBeenCalledTimes(1) + }) + }) + + describe('Overlay cleanup', () => { + it('should close confirm dialog when alert dialog requests close', () => { + const hideConfirm = vi.fn() + mockUseBoolean + .mockImplementationOnce(() => [true, { setFalse: hideConfirm, setTrue: vi.fn() }]) + .mockImplementationOnce((initial: boolean) => [initial, { setFalse: vi.fn(), setTrue: vi.fn() }]) + .mockImplementationOnce((initial: boolean) => [initial, { setFalse: vi.fn(), setTrue: vi.fn() }]) + .mockImplementationOnce((initial: boolean) => [initial, { setFalse: vi.fn(), setTrue: vi.fn() }]) + + render() + + fireEvent.click(screen.getByTestId('alert-dialog-close')) + + expect(hideConfirm).toHaveBeenCalledTimes(1) + }) + }) + + describe('Publish params', () => { + it('should publish as template with empty pipeline id fallback', async () => { + mockPipelineId = undefined + mockUseBoolean + .mockImplementationOnce((initial: boolean) => [initial, { setFalse: vi.fn(), setTrue: vi.fn() }]) + .mockImplementationOnce((initial: boolean) => [initial, { setFalse: vi.fn(), setTrue: vi.fn() }]) + .mockImplementationOnce(() => [true, { setFalse: vi.fn(), setTrue: vi.fn() }]) + .mockImplementationOnce((initial: boolean) => [initial, { setFalse: vi.fn(), setTrue: vi.fn() }]) + render() + + fireEvent.click(screen.getByTestId('publish-as-confirm')) + + expect(mockPublishAsCustomizedPipeline).toHaveBeenCalledWith({ + pipelineId: '', + name: 'My Pipeline', + icon_info: { icon_type: 'emoji' }, + description: 'desc', + }) + }) }) describe('Time formatting', () => { diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/index.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/index.tsx index 3ea9aa0c1f..649b06ebca 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/index.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/index.tsx @@ -1,4 +1,5 @@ import { Button } from '@langgenius/dify-ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { RiArrowDownSLine } from '@remixicon/react' import { memo, @@ -6,11 +7,6 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useNodesSyncDraft } from '@/app/components/workflow/hooks' import Popup from './popup' @@ -26,28 +22,31 @@ const Publisher = () => { }, [handleSyncWorkflowDraft]) return ( - - handleOpenChange(!open)}> - - - - - - + + {t('common.publish', { ns: 'workflow' })} + + + )} + /> + + handleOpenChange(false)} /> + + ) } diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx index 9cb026dffe..31f5957029 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/popup.tsx @@ -39,7 +39,11 @@ import { usePublishWorkflow } from '@/service/use-workflow' import PublishAsKnowledgePipelineModal from '../../publish-as-knowledge-pipeline-modal' const PUBLISH_SHORTCUT = ['ctrl', '⇧', 'P'] -const Popup = () => { +type PopupProps = { + onRequestClose?: () => void +} + +const Popup = ({ onRequestClose }: PopupProps) => { const { t } = useTranslation() const { datasetId } = useParams() const { push } = useRouter() @@ -70,6 +74,7 @@ const Popup = () => { const checked = await handleCheckBeforePublish() if (checked) { if (!publishedAt && !confirmVisible) { + onRequestClose?.() showConfirm() return } @@ -114,7 +119,7 @@ const Popup = () => { if (confirmVisible) hideConfirm() } - }, [publishing, handleCheckBeforePublish, publishedAt, confirmVisible, showPublishing, publishWorkflow, pipelineId, datasetId, showConfirm, t, workflowStore, mutateDatasetRes, invalidPublishedPipelineInfo, invalidDatasetList, hidePublishing, hideConfirm]) + }, [publishing, handleCheckBeforePublish, publishedAt, confirmVisible, showPublishing, publishWorkflow, pipelineId, datasetId, showConfirm, t, workflowStore, mutateDatasetRes, invalidPublishedPipelineInfo, invalidDatasetList, hidePublishing, hideConfirm, onRequestClose]) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => { e.preventDefault() if (published) @@ -155,13 +160,14 @@ const Popup = () => { hidePublishingAsCustomizedPipeline() hidePublishAsKnowledgePipelineModal() } - }, [showPublishingAsCustomizedPipeline, publishAsCustomizedPipeline, pipelineId, t, invalidCustomizedTemplateList, hidePublishingAsCustomizedPipeline, hidePublishAsKnowledgePipelineModal]) + }, [showPublishingAsCustomizedPipeline, publishAsCustomizedPipeline, pipelineId, t, invalidCustomizedTemplateList, hidePublishingAsCustomizedPipeline, hidePublishAsKnowledgePipelineModal, docLink]) const handleClickPublishAsKnowledgePipeline = useCallback(() => { + onRequestClose?.() if (!isAllowPublishAsCustomKnowledgePipelineTemplate) setShowPricingModal() else setShowPublishAsKnowledgePipelineModal() - }, [isAllowPublishAsCustomKnowledgePipelineTemplate, setShowPublishAsKnowledgePipelineModal, setShowPricingModal]) + }, [isAllowPublishAsCustomKnowledgePipelineTemplate, onRequestClose, setShowPublishAsKnowledgePipelineModal, setShowPricingModal]) return (
diff --git a/web/app/components/workflow/block-selector/__tests__/main.spec.tsx b/web/app/components/workflow/block-selector/__tests__/main.spec.tsx index 2cb0d3e98f..8dc1e81379 100644 --- a/web/app/components/workflow/block-selector/__tests__/main.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/main.spec.tsx @@ -86,4 +86,53 @@ describe('NodeSelector', () => { expect(reopenedInput.value).toBe('') expect(screen.getByText('End')).toBeInTheDocument() }) + + it('does not open or emit open changes when disabled', async () => { + const user = userEvent.setup() + const onOpenChange = vi.fn() + + renderWorkflowComponent( + ( + + )} + />, + ) + + await user.click(screen.getByRole('button', { name: 'selector-closed' })) + + expect(onOpenChange).not.toHaveBeenCalled() + expect(screen.queryByPlaceholderText('workflow.tabs.searchBlock')).not.toBeInTheDocument() + }) + + it('preserves the child trigger click handler when rendered as child', async () => { + const user = userEvent.setup() + const onTriggerClick = vi.fn() + + renderWorkflowComponent( + ( + + )} + />, + ) + + await user.click(screen.getByRole('button', { name: 'open-selector' })) + + expect(onTriggerClick).toHaveBeenCalledTimes(1) + expect(screen.getByPlaceholderText('workflow.tabs.searchBlock')).toBeInTheDocument() + }) }) diff --git a/web/app/components/workflow/block-selector/main.tsx b/web/app/components/workflow/block-selector/main.tsx index 96f5a98aed..b583170ae7 100644 --- a/web/app/components/workflow/block-selector/main.tsx +++ b/web/app/components/workflow/block-selector/main.tsx @@ -5,6 +5,7 @@ import type { import type { FC, MouseEventHandler, + MouseEvent as ReactMouseEvent, } from 'react' import type { CommonNodeType, @@ -12,6 +13,12 @@ import type { OnSelectBlock, ToolWithProvider, } from '../types' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' +import * as React from 'react' import { memo, useCallback, @@ -23,11 +30,6 @@ import { Plus02, } from '@/app/components/base/icons/src/vender/line/general' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import SearchBox from '@/app/components/plugins/marketplace/search-box' import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { BlockEnum, isTriggerNode } from '../types' @@ -121,6 +123,9 @@ const NodeSelector: FC = ({ const canSelectUserInput = allowUserInputSelection ?? defaultAllowUserInputSelection const open = openFromProps === undefined ? localOpen : openFromProps const handleOpenChange = useCallback((newOpen: boolean) => { + if (disabled) + return + setLocalOpen(newOpen) if (!newOpen) @@ -128,13 +133,10 @@ const NodeSelector: FC = ({ if (onOpenChange) onOpenChange(newOpen) - }, [onOpenChange]) - const handleTrigger = useCallback>((e) => { - if (disabled) - return + }, [disabled, onOpenChange]) + const handleTrigger = useCallback>((e) => { e.stopPropagation() - handleOpenChange(!open) - }, [handleOpenChange, open, disabled]) + }, []) const handleSelect = useCallback((type, pluginDefaultValue) => { handleOpenChange(false) @@ -174,36 +176,61 @@ const NodeSelector: FC = ({ return '' }, [activeTab, t]) + const defaultTriggerElement = ( +
+ +
+ ) + const triggerElement = trigger ? trigger(open) : defaultTriggerElement + const triggerElementProps = React.isValidElement(triggerElement) + ? (triggerElement.props as { + onClick?: MouseEventHandler + }) + : null + const resolvedTriggerElement = asChild && React.isValidElement(triggerElement) + ? React.cloneElement( + triggerElement as React.ReactElement<{ + onClick?: MouseEventHandler + }>, + { + onClick: (e: ReactMouseEvent) => { + handleTrigger(e) + if (typeof triggerElementProps?.onClick === 'function') + triggerElementProps.onClick(e) + }, + }, + ) + : ( +
+ {triggerElement} +
+ ) + const resolvedOffset = typeof offset === 'number' || typeof offset === 'function' ? undefined : offset + const sideOffset = typeof offset === 'number' ? offset : (resolvedOffset?.mainAxis ?? 0) + const alignOffset = typeof offset === 'number' ? 0 : (resolvedOffset?.crossAxis ?? 0) + const nativeButton = asChild + && React.isValidElement(triggerElement) + && (typeof triggerElement.type !== 'string' || triggerElement.type === 'button') + return ( - - + - { - trigger - ? trigger(open) - : ( -
- -
- ) - } -
-
= ({ forceShowStartContent={forceShowStartContent} />
-
-
+ + ) } diff --git a/web/app/components/workflow/header/__tests__/view-history.spec.tsx b/web/app/components/workflow/header/__tests__/view-history.spec.tsx index 4481c72cf7..93e0b56125 100644 --- a/web/app/components/workflow/header/__tests__/view-history.spec.tsx +++ b/web/app/components/workflow/header/__tests__/view-history.spec.tsx @@ -10,14 +10,13 @@ const mockFormatTimeFromNow = vi.fn((value: number) => `from-now:${value}`) const mockCloseAllInputFieldPanels = vi.fn() const mockHandleNodesCancelSelected = vi.fn() const mockHandleCancelDebugAndPreviewPanel = vi.fn() +const mockHandleBackupDraft = vi.fn() const mockFormatWorkflowRunIdentifier = vi.fn((finishedAt?: number, status?: string) => ` (${status || finishedAt || 'unknown'})`) let mockIsChatMode = false -vi.mock('../../hooks', async () => { - const actual = await vi.importActual('../../hooks') +vi.mock('../../hooks', () => { return { - ...actual, useIsChatMode: () => mockIsChatMode, useNodesInteractions: () => ({ handleNodesCancelSelected: mockHandleNodesCancelSelected, @@ -25,6 +24,9 @@ vi.mock('../../hooks', async () => { useWorkflowInteractions: () => ({ handleCancelDebugAndPreviewPanel: mockHandleCancelDebugAndPreviewPanel, }), + useWorkflowRun: () => ({ + handleBackupDraft: mockHandleBackupDraft, + }), } }) @@ -48,38 +50,46 @@ vi.mock('@/app/components/base/loading', () => ({ default: () =>
, })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ children }: { children?: React.ReactNode }) => <>{children}, +vi.mock('@langgenius/dify-ui/toast', () => ({ + toast: { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + }, })) -vi.mock('@/app/components/base/portal-to-follow-elem', () => { - const PortalContext = React.createContext({ open: false }) +vi.mock('@langgenius/dify-ui/button', () => ({ + Button: ({ + children, + ...props + }: React.ButtonHTMLAttributes) => ( + + ), +})) - return { - PortalToFollowElem: ({ - children, - open, - }: { - children?: React.ReactNode - open: boolean - }) => {children}, - PortalToFollowElemTrigger: ({ - children, - onClick, - }: { - children?: React.ReactNode - onClick?: () => void - }) =>
{children}
, - PortalToFollowElemContent: ({ - children, - }: { - children?: React.ReactNode - }) => { - const { open } = React.useContext(PortalContext) - return open ?
{children}
: null - }, - } -}) +vi.mock('@langgenius/dify-ui/tooltip', () => ({ + Tooltip: ({ children }: { children?: React.ReactNode }) => <>{children}, + TooltipTrigger: ({ + children, + render, + }: { + children?: React.ReactNode + render?: React.ReactElement + }) => { + if (render && React.isValidElement(render)) { + const renderElement = render as React.ReactElement<{ children?: React.ReactNode }> + return React.cloneElement(renderElement, renderElement.props, children) + } + + return <>{children} + }, + TooltipContent: ({ children }: { children?: React.ReactNode }) => <>{children}, +})) + +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) vi.mock('../../utils', async () => { const actual = await vi.importActual('../../utils') @@ -130,7 +140,7 @@ describe('ViewHistory', () => { }) expect(mockUseWorkflowRunHistory).toHaveBeenCalledWith('/history', false) - expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument() + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: 'workflow.common.showRunHistory' })) @@ -165,7 +175,6 @@ describe('ViewHistory', () => { }) it('renders workflow run history items and updates the workflow store when one is selected', () => { - const handleBackupDraft = vi.fn() const pausedRun = createHistoryItem({ id: 'run-paused', status: WorkflowRunningStatus.Paused, @@ -199,9 +208,6 @@ describe('ViewHistory', () => { showEnvPanel: true, controlMode: ControlMode.Pointer, }, - hooksStoreProps: { - handleBackupDraft, - }, }) fireEvent.click(screen.getByRole('button', { name: 'workflow.common.showRunHistory' })) @@ -217,7 +223,7 @@ describe('ViewHistory', () => { expect(store.getState().showEnvPanel).toBe(false) expect(store.getState().controlMode).toBe(ControlMode.Hand) expect(mockCloseAllInputFieldPanels).toHaveBeenCalledTimes(1) - expect(handleBackupDraft).toHaveBeenCalledTimes(1) + expect(mockHandleBackupDraft).toHaveBeenCalledTimes(1) expect(mockHandleNodesCancelSelected).toHaveBeenCalledTimes(1) expect(mockHandleCancelDebugAndPreviewPanel).toHaveBeenCalledTimes(1) }) @@ -271,6 +277,6 @@ describe('ViewHistory', () => { fireEvent.click(screen.getByRole('button', { name: 'common.operation.close' })) expect(onClearLogAndMessageModal).toHaveBeenCalledTimes(1) - expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument() + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx index 3f98f6cb6c..bde9f370c9 100644 --- a/web/app/components/workflow/header/view-history.tsx +++ b/web/app/components/workflow/header/view-history.tsx @@ -1,16 +1,20 @@ import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@langgenius/dify-ui/tooltip' import { memo, useState, } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Tooltip from '@/app/components/base/tooltip' import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' import { useStore, @@ -61,52 +65,60 @@ const ViewHistory = ({ return ( ( - - setOpen(v => !v)}> - { - withText && ( - )} - > - - {t('common.showRunHistory', { ns: 'workflow' })} - + /> ) - } - { - !withText && ( - - + { + onClearLogAndMessageModal?.() + }} + > + + + )} + /> + + + {t('common.viewRunHistory', { ns: 'workflow' })} + - ) - } - - + )} +
- - + + ) ) } diff --git a/web/app/components/workflow/header/view-workflow-history.tsx b/web/app/components/workflow/header/view-workflow-history.tsx index 9f70187941..036f27d38d 100644 --- a/web/app/components/workflow/header/view-workflow-history.tsx +++ b/web/app/components/workflow/header/view-workflow-history.tsx @@ -1,5 +1,10 @@ import type { WorkflowHistoryState } from '../workflow-history-store' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiCloseLine, RiHistoryLine, @@ -13,11 +18,6 @@ import { import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import { useStore as useAppStore } from '@/app/components/app/store' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import Divider from '../../base/divider' import { collaborationManager } from '../collaboration/core/collaboration-manager' import { @@ -91,12 +91,20 @@ const ViewWorkflowHistory = () => { }, [t]) const calculateChangeList: ChangeHistoryList = useMemo(() => { - const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial, index: number) => { - const nodes = (state.nodes || store.getState().nodes) || [] - const nodeId = state?.workflowHistoryEventMeta?.nodeId + const filterList = ( + list: Array | undefined>, + startIndex = 0, + reverse = false, + ) => list.flatMap((state, index) => { + if (!state) + return [] + + const nodes = state.nodes || store.getState().nodes || [] + const nodeId = state.workflowHistoryEventMeta?.nodeId const targetTitle = nodes.find(n => n.id === nodeId)?.data?.title ?? '' - return { - label: state.workflowHistoryEvent && getHistoryLabel(state.workflowHistoryEvent), + + return [{ + label: state.workflowHistoryEvent ? getHistoryLabel(state.workflowHistoryEvent) : '', index: reverse ? list.length - 1 - index - startIndex : index - startIndex, state: { ...state, @@ -107,8 +115,8 @@ const ViewWorkflowHistory = () => { } : undefined, }, - } - }).filter(Boolean) + }] + }) const historyData = { pastStates: filterList(pastStates, pastStates.length).reverse(), @@ -132,35 +140,42 @@ const ViewWorkflowHistory = () => { return ( ( - { + if (nodesReadOnly) + return + setOpen(nextOpen) + }} > - !nodesReadOnly && setOpen(v => !v)}> - -
{ - if (nodesReadOnly) - return - setCurrentLogItem() - setShowMessageLogModal(false) - }} - > - -
-
-
- + + { + if (nodesReadOnly) + return + setCurrentLogItem() + setShowMessageLogModal(false) + }} + > + +
+ )} + /> + +
@@ -293,8 +308,8 @@ const ViewWorkflowHistory = () => {
{t('changeHistory.hintText', { ns: 'workflow' })}
- - + + ) ) } diff --git a/web/app/components/workflow/nodes/human-input/components/__tests__/button-style-dropdown.spec.tsx b/web/app/components/workflow/nodes/human-input/components/__tests__/button-style-dropdown.spec.tsx index 056ebf4795..fe288899bd 100644 --- a/web/app/components/workflow/nodes/human-input/components/__tests__/button-style-dropdown.spec.tsx +++ b/web/app/components/workflow/nodes/human-input/components/__tests__/button-style-dropdown.spec.tsx @@ -21,42 +21,7 @@ vi.mock('@langgenius/dify-ui/button', () => ({ }, })) -vi.mock('@/app/components/base/portal-to-follow-elem', () => { - const OpenContext = React.createContext(false) - - return { - PortalToFollowElem: ({ - open, - children, - }: { - open: boolean - children?: React.ReactNode - }) => ( - -
{children}
-
- ), - PortalToFollowElemTrigger: ({ - children, - onClick, - }: { - children?: React.ReactNode - onClick?: () => void - }) => ( - - ), - PortalToFollowElemContent: ({ - children, - }: { - children?: React.ReactNode - }) => { - const open = React.use(OpenContext) - return open ?
{children}
: null - }, - } -}) +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) describe('ButtonStyleDropdown', () => { const onChange = vi.fn() @@ -80,10 +45,10 @@ describe('ButtonStyleDropdown', () => { expect(mockButton).toHaveBeenCalledWith(expect.objectContaining({ variant: 'ghost', })) - expect(screen.getByTestId('portal'))!.toHaveAttribute('data-open', 'false') + expect(screen.getByTestId('popover'))!.toHaveAttribute('data-open', 'false') - fireEvent.click(screen.getByTestId('portal-trigger')) - expect(screen.getByTestId('portal'))!.toHaveAttribute('data-open', 'true') + fireEvent.click(screen.getByTestId('popover-trigger')) + expect(screen.getByTestId('popover'))!.toHaveAttribute('data-open', 'true') expect(screen.getByText('nodes.humanInput.userActions.chooseStyle'))!.toBeInTheDocument() fireEvent.click(screen.getByTestId('button-primary').parentElement as HTMLElement) @@ -111,10 +76,10 @@ describe('ButtonStyleDropdown', () => { variant: 'secondary', })) - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByTestId('popover-trigger')) - expect(screen.getByTestId('portal'))!.toHaveAttribute('data-open', 'false') - expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument() + expect(screen.getByTestId('popover'))!.toHaveAttribute('data-open', 'false') + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() expect(onChange).not.toHaveBeenCalled() }) diff --git a/web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx b/web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx index 44ddbbfa34..688f7a62f6 100644 --- a/web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx +++ b/web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx @@ -1,17 +1,17 @@ import type { FC } from 'react' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiFontSize, } from '@remixicon/react' import * as React from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { UserActionButtonType } from '../types' const i18nPrefix = 'nodes.humanInput' @@ -45,23 +45,29 @@ const ButtonStyleDropdown: FC = ({ }, [data]) return ( - { + if (readonly) + return + setOpen(nextOpen) }} > - !readonly && setOpen(v => !v)}> -
- -
-
- + + +
+ )} + /> +
{t(`${i18nPrefix}.userActions.chooseStyle`, { ns: 'workflow' })}
@@ -103,8 +109,8 @@ const ButtonStyleDropdown: FC = ({
- - + + ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx index dd530ae679..da17b4b2b3 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx @@ -1,16 +1,16 @@ import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import { Button } from '@langgenius/dify-ui/button' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiFilter3Line } from '@remixicon/react' import { useEffect, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import MetadataPanel from './metadata-panel' const MetadataTrigger = ({ @@ -40,25 +40,29 @@ const MetadataTrigger = ({ }, [metadataFilteringConditions, metadataList, handleRemoveCondition, selectedDatasetsLoaded]) return ( - - setOpen(!open)}> - - - + + + {t('nodes.knowledgeRetrieval.metadata.panel.conditions', { ns: 'workflow' })} +
+ {metadataFilteringConditions?.conditions.length || 0} +
+ + )} + /> + setOpen(false)} @@ -66,8 +70,8 @@ const MetadataTrigger = ({ handleRemoveCondition={handleRemoveCondition} {...restProps} /> -
-
+ + ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx index da71682b35..953e8474e4 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx @@ -7,16 +7,16 @@ import type { DataSet } from '@/models/datasets' import type { DatasetConfigs } from '@/models/debug' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiEqualizer2Line } from '@remixicon/react' import * as React from 'react' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import ConfigRetrievalContent from '@/app/components/app/configuration/dataset-config/params-config/config-content' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { DATASET_DEFAULT } from '@/config' import { RETRIEVE_TYPE } from '@/types/app' @@ -114,32 +114,33 @@ const RetrievalConfig: FC = ({ }, [onMultipleRetrievalConfigChange, retrieval_mode, onRetrievalModeChange]) return ( - { + if (readonly) + return + handleOpen(nextOpen) }} > - { - if (readonly) - return - handleOpen(!rerankModalOpen) - }} + + + {t('retrievalSettings', { ns: 'dataset' })} + + )} + /> + - - -
= ({ onSingleRetrievalModelParamsChange={onSingleRetrievalModelParamsChange} />
-
-
+ + ) } export default React.memo(RetrievalConfig) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx index c949b89adb..bf15761d9d 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx @@ -3,14 +3,14 @@ import type { SchemaRoot } from '../../../types' import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { CompletionParams, Model } from '@/types/app' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { toast } from '@langgenius/dify-ui/toast' import * as React from 'react' -import { useCallback, useEffect, useState } from 'react' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +import { useCallback, useState } from 'react' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import useTheme from '@/hooks/use-theme' @@ -27,61 +27,68 @@ type JsonSchemaGeneratorProps = { crossAxisOffset?: number } -enum GeneratorView { - promptEditor = 'promptEditor', - result = 'result', +const GENERATOR_VIEWS = { + promptEditor: 'promptEditor', + result: 'result', +} as const + +type GeneratorView = typeof GENERATOR_VIEWS[keyof typeof GENERATOR_VIEWS] + +const createEmptyModel = (): Model => ({ + name: '', + provider: '', + mode: ModelModeType.completion, + completion_params: {} as CompletionParams, +}) + +const getStoredModel = (): Model | null => { + if (typeof window === 'undefined') + return null + + const savedModel = window.localStorage.getItem('auto-gen-model') + + if (!savedModel) + return null + + return JSON.parse(savedModel) as Model } const JsonSchemaGenerator: FC = ({ onApply, crossAxisOffset, }) => { - const localModel = localStorage.getItem('auto-gen-model') - ? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model - : null const [open, setOpen] = useState(false) - const [view, setView] = useState(GeneratorView.promptEditor) - const [model, setModel] = useState(localModel || { - name: '', - provider: '', - mode: ModelModeType.completion, - completion_params: {} as CompletionParams, - }) + const [view, setView] = useState(GENERATOR_VIEWS.promptEditor) + const [model, setModel] = useState(() => getStoredModel()) const [instruction, setInstruction] = useState('') const [schema, setSchema] = useState(null) const { theme } = useTheme() const { defaultModel, } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration) + const resolvedModel = React.useMemo(() => { + if (model) + return model + + if (!defaultModel) + return createEmptyModel() + + return { + ...createEmptyModel(), + name: defaultModel.model, + provider: defaultModel.provider.provider, + } + }, [defaultModel, model]) const advancedEditing = useVisualEditorStore(state => state.advancedEditing) const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField) const { emit } = useMittContext() const SchemaGenerator = theme === Theme.light ? SchemaGeneratorLight : SchemaGeneratorDark - useEffect(() => { - if (defaultModel) { - const localModel = localStorage.getItem('auto-gen-model') - ? JSON.parse(localStorage.getItem('auto-gen-model') || '') - : null - if (localModel) { - setModel(localModel) - } - else { - setModel(prev => ({ - ...prev, - name: defaultModel.model, - provider: defaultModel.provider.provider, - })) - } - } - }, [defaultModel]) - const handleTrigger = useCallback((e: React.MouseEvent) => { e.stopPropagation() if (advancedEditing || isAddingNewField) emit('quitEditing', {}) - setOpen(!open) - }, [open, advancedEditing, isAddingNewField, emit]) + }, [advancedEditing, isAddingNewField, emit]) const onClose = useCallback(() => { setOpen(false) @@ -89,39 +96,39 @@ const JsonSchemaGenerator: FC = ({ const handleModelChange = useCallback((newValue: { modelId: string, provider: string, mode?: string, features?: string[] }) => { const newModel = { - ...model, + ...resolvedModel, provider: newValue.provider, name: newValue.modelId, mode: newValue.mode as ModelModeType, } setModel(newModel) - localStorage.setItem('auto-gen-model', JSON.stringify(newModel)) - }, [model, setModel]) + window.localStorage.setItem('auto-gen-model', JSON.stringify(newModel)) + }, [resolvedModel]) const handleCompletionParamsChange = useCallback((newParams: FormValue) => { const newModel = { - ...model, + ...resolvedModel, completion_params: newParams as CompletionParams, } setModel(newModel) - localStorage.setItem('auto-gen-model', JSON.stringify(newModel)) - }, [model, setModel]) + window.localStorage.setItem('auto-gen-model', JSON.stringify(newModel)) + }, [resolvedModel]) const { mutateAsync: generateStructuredOutputRules, isPending: isGenerating } = useGenerateStructuredOutputRules() const generateSchema = useCallback(async () => { - const { output, error } = await generateStructuredOutputRules({ instruction, model_config: model! }) + const { output, error } = await generateStructuredOutputRules({ instruction, model_config: resolvedModel }) if (error) { toast.error(error) setSchema(null) - setView(GeneratorView.promptEditor) + setView(GENERATOR_VIEWS.promptEditor) return } return output - }, [instruction, model, generateStructuredOutputRules]) + }, [generateStructuredOutputRules, instruction, resolvedModel]) const handleGenerate = useCallback(async () => { - setView(GeneratorView.result) + setView(GENERATOR_VIEWS.result) const output = await generateSchema() if (output === undefined) return @@ -129,7 +136,7 @@ const JsonSchemaGenerator: FC = ({ }, [generateSchema]) const goBackToPromptEditor = () => { - setView(GeneratorView.promptEditor) + setView(GENERATOR_VIEWS.promptEditor) } const handleRegenerate = useCallback(async () => { @@ -145,31 +152,34 @@ const JsonSchemaGenerator: FC = ({ } return ( - - - - - - {view === GeneratorView.promptEditor && ( + + + + )} + /> + + {view === GENERATOR_VIEWS.promptEditor && ( = ({ onModelChange={handleModelChange} /> )} - {view === GeneratorView.result && ( + {view === GENERATOR_VIEWS.result && ( = ({ onClose={onClose} /> )} - - + + ) } diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/color-picker.spec.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/color-picker.spec.tsx index 9f36b4a7ac..b70559aa6b 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/color-picker.spec.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/color-picker.spec.tsx @@ -1,32 +1,32 @@ -import { fireEvent, render, waitFor } from '@testing-library/react' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { NoteTheme } from '../../../types' -import ColorPicker, { COLOR_LIST } from '../color-picker' +import ColorPicker from '../color-picker' + +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) describe('NoteEditor ColorPicker', () => { it('should open the palette and apply the selected theme', async () => { const onThemeChange = vi.fn() - const { container } = render( + render( , ) - const trigger = container.querySelector('[data-state="closed"]') as HTMLElement + fireEvent.click(screen.getByTestId('popover-trigger')) - fireEvent.click(trigger) - - const popup = document.body.querySelector('[role="tooltip"]') + const popup = screen.getByTestId('popover-content') expect(popup).toBeInTheDocument() - const options = popup?.querySelectorAll('.group.relative') + const options = popup.querySelectorAll('.group.relative') - expect(options).toHaveLength(COLOR_LIST.length) + expect(options).toHaveLength(6) - fireEvent.click(options?.[COLOR_LIST.length - 1] as Element) + fireEvent.click(options[5] as Element) expect(onThemeChange).toHaveBeenCalledWith(NoteTheme.violet) await waitFor(() => { - expect(document.body.querySelector('[role="tooltip"]')).not.toBeInTheDocument() + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() }) }) }) diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/font-size-selector.spec.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/font-size-selector.spec.tsx index e94b66e695..bce7bb326d 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/font-size-selector.spec.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/font-size-selector.spec.tsx @@ -1,6 +1,8 @@ import { fireEvent, render, screen } from '@testing-library/react' import FontSizeSelector from '../font-size-selector' +vi.mock('@langgenius/dify-ui/popover', () => import('@/__mocks__/base-ui-popover')) + const { mockHandleFontSize, mockHandleOpenFontSizeSelector, @@ -52,4 +54,12 @@ describe('NoteEditor FontSizeSelector', () => { expect(mockHandleFontSize).toHaveBeenCalledWith('16px') expect(mockHandleOpenFontSizeSelector).toHaveBeenCalledWith(false) }) + + it('should fall back to the small label when current font size is unknown', () => { + mockFontSize = '18px' + + render() + + expect(screen.getByText('workflow.nodes.note.editor.small')).toBeInTheDocument() + }) }) diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/index.spec.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/index.spec.tsx index cee2b7fd40..cb50f641b4 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/index.spec.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/__tests__/index.spec.tsx @@ -76,11 +76,14 @@ describe('NoteEditor Toolbar', () => { expect(screen.getByText('workflow.nodes.note.editor.medium')).toBeInTheDocument() - const triggers = container.querySelectorAll('[data-state="closed"]') + const buttons = container.querySelectorAll('button[type="button"]') + fireEvent.click(buttons[0] as HTMLElement) - fireEvent.click(triggers[0] as HTMLElement) + await waitFor(() => { + expect(document.body.querySelectorAll('.group.relative').length).toBeGreaterThan(0) + }) - const colorOptions = document.body.querySelectorAll('[role="tooltip"] .group.relative') + const colorOptions = document.body.querySelectorAll('.group.relative') fireEvent.click(colorOptions[colorOptions.length - 1] as Element) diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx index e8c5055962..2b12a947ea 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx @@ -1,17 +1,17 @@ import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { memo, useState, } from 'react' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { THEME_MAP } from '../../constants' import { NoteTheme } from '../../types' -export const COLOR_LIST = [ +const COLOR_LIST = [ { key: NoteTheme.blue, inner: THEME_MAP[NoteTheme.blue]!.title, @@ -55,28 +55,35 @@ const ColorPicker = ({ const [open, setOpen] = useState(false) return ( - - setOpen(!open)}> -
-
-
-
-
- +
+
+ + )} + /> +
{ COLOR_LIST.map(color => ( @@ -107,8 +114,8 @@ const ColorPicker = ({ )) }
-
-
+ + ) } diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx index a217d7de72..13da51deb7 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx @@ -1,13 +1,13 @@ import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiFontSize } from '@remixicon/react' import { memo } from 'react' import { useTranslation } from 'react-i18next' import { Check } from '@/app/components/base/icons/src/vender/line/general' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useFontSize } from './hooks' const FontSizeSelector = () => { @@ -34,23 +34,30 @@ const FontSizeSelector = () => { } = useFontSize() return ( - - handleOpenFontSizeSelector(!fontSizeSelectorShow)}> -
+ + {FONT_SIZE_LIST.find(font => font.key === fontSize)?.value || t('nodes.note.editor.small', { ns: 'workflow' })} + )} - > - - {FONT_SIZE_LIST.find(font => font.key === fontSize)?.value || t('nodes.note.editor.small', { ns: 'workflow' })} -
-
- + /> +
{ FONT_SIZE_LIST.map(font => ( @@ -77,8 +84,8 @@ const FontSizeSelector = () => { )) }
-
-
+ + ) } diff --git a/web/app/components/workflow/panel/version-history-panel/filter/index.tsx b/web/app/components/workflow/panel/version-history-panel/filter/index.tsx index f48804d921..bb94e3727a 100644 --- a/web/app/components/workflow/panel/version-history-panel/filter/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/filter/index.tsx @@ -1,14 +1,14 @@ import type { FC } from 'react' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiFilter3Line } from '@remixicon/react' import * as React from 'react' import { useCallback, useState } from 'react' import Divider from '@/app/components/base/divider' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { WorkflowVersionFilterOptions } from '../../../types' import FilterItem from './filter-item' import FilterSwitch from './filter-switch' @@ -37,26 +37,28 @@ const Filter: FC = ({ const isFiltering = filterValue !== WorkflowVersionFilterOptions.all || isOnlyShowNamedVersions return ( - - setOpen(v => !v)}> -
- -
-
- + + +
+ )} + /> +
{ @@ -75,8 +77,8 @@ const Filter: FC = ({
- - + + ) } diff --git a/web/docs/overlay-migration.md b/web/docs/overlay-migration.md index 0fa584ecfc..73c0f02d9d 100644 --- a/web/docs/overlay-migration.md +++ b/web/docs/overlay-migration.md @@ -55,7 +55,7 @@ pnpm -C web lint:fix --prune-suppressions ## z-index strategy -All new overlay primitives in `@langgenius/dify-ui` share a single z-index value: +All new overlay primitives in `@langgenius/dify-ui/*` share a single z-index value: **`z-1002`**, except Toast which stays one layer above at **`z-1003`**. ### Why z-[1002]? @@ -94,7 +94,7 @@ back to `z-9999`. Once all legacy overlays are removed: -1. Reduce `z-1002` back to `z-50` across all `@langgenius/dify-ui` primitives. +1. Reduce `z-1002` back to `z-50` across all `@langgenius/dify-ui/*` primitives. 1. Reduce Toast from `z-1003` to `z-51`. 1. Remove this section from the migration guide. diff --git a/web/eslint.constants.mjs b/web/eslint.constants.mjs index 5a2330c00e..1c09cbcb23 100644 --- a/web/eslint.constants.mjs +++ b/web/eslint.constants.mjs @@ -64,20 +64,13 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [ export const OVERLAY_MIGRATION_LEGACY_BASE_FILES = [ 'app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx', 'app/components/base/chat/chat-with-history/header/operation.tsx', - 'app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx', 'app/components/base/chat/chat-with-history/sidebar/operation.tsx', 'app/components/base/chat/chat/citation/popup.tsx', 'app/components/base/chat/chat/citation/progress-tooltip.tsx', 'app/components/base/chat/chat/citation/tooltip.tsx', - 'app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx', 'app/components/base/chip/index.tsx', 'app/components/base/date-and-time-picker/date-picker/index.tsx', 'app/components/base/date-and-time-picker/time-picker/index.tsx', - 'app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx', - 'app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx', - 'app/components/base/file-uploader/file-from-link-or-local/index.tsx', - 'app/components/base/image-uploader/chat-image-uploader.tsx', - 'app/components/base/image-uploader/text-generation-image-uploader.tsx', 'app/components/base/modal/modal.tsx', 'app/components/base/prompt-editor/plugins/context-block/component.tsx', 'app/components/base/prompt-editor/plugins/history-block/component.tsx', From c2a59620237dae3e25199387fadc128a51a18401 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:05:22 +0800 Subject: [PATCH 017/253] feat(dify-ui): add PreviewCard primitive (#35434) Signed-off-by: yyh Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .gitignore | 4 + eslint-suppressions.json | 29 --- packages/dify-ui/AGENTS.md | 23 ++ packages/dify-ui/package.json | 4 + .../dify-ui/src/popover/index.stories.tsx | 44 +++- .../src/preview-card/__tests__/index.spec.tsx | 127 +++++++++++ .../src/preview-card/index.stories.tsx | 213 ++++++++++++++++++ packages/dify-ui/src/preview-card/index.tsx | 81 +++++++ .../src/tooltip/__tests__/index.spec.tsx | 21 +- .../dify-ui/src/tooltip/index.stories.tsx | 209 ++++++----------- packages/dify-ui/src/tooltip/index.tsx | 31 ++- web/app/components/base/infotip/index.tsx | 82 +++++++ .../__tests__/variable-block.spec.tsx | 11 +- .../hitl-input-block/variable-block.tsx | 17 +- .../workflow-variable-block/component.tsx | 14 +- .../model-parameter-modal/parameter-item.tsx | 21 +- .../model-selector/popup-item.tsx | 24 +- .../model-selector/popup.tsx | 2 +- .../usage-priority-section.tsx | 19 +- .../provider-added-card/quota-panel.tsx | 17 +- .../system-model-selector/index.tsx | 28 +-- .../workflow/block-selector/blocks.tsx | 69 +++--- .../block-selector/featured-tools.tsx | 117 +++++----- .../block-selector/featured-triggers.tsx | 117 +++++----- .../workflow/block-selector/start-blocks.tsx | 63 +++--- .../block-selector/tool/action-item.tsx | 107 ++++----- .../trigger-plugin/action-item.tsx | 97 ++++---- .../var-reference-picker.trigger.spec.tsx | 2 +- .../variable/var-reference-picker.trigger.tsx | 193 ++++++++-------- .../variable/var-reference-picker.tsx | 26 ++- 30 files changed, 1151 insertions(+), 661 deletions(-) create mode 100644 packages/dify-ui/src/preview-card/__tests__/index.spec.tsx create mode 100644 packages/dify-ui/src/preview-card/index.stories.tsx create mode 100644 packages/dify-ui/src/preview-card/index.tsx create mode 100644 web/app/components/base/infotip/index.tsx diff --git a/.gitignore b/.gitignore index 3493a7c756..836bddbb49 100644 --- a/.gitignore +++ b/.gitignore @@ -237,6 +237,10 @@ scripts/stress-test/reports/ .playwright-mcp/ .serena/ +# vitest browser mode attachments (failure screenshots, traces, etc.) +.vitest-attachments/ +**/__screenshots__/ + # settings *.local.json *.local.md diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 96af36d27a..b4840a2eff 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1931,11 +1931,6 @@ "count": 2 } }, - "web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/base/prompt-editor/plugins/last-run-block/index.tsx": { "no-barrel-files/no-barrel-files": { "count": 3 @@ -4087,15 +4082,7 @@ "count": 1 } }, - "web/app/components/workflow/block-selector/blocks.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/block-selector/featured-tools.tsx": { - "no-restricted-imports": { - "count": 1 - }, "react/set-state-in-effect": { "count": 2 }, @@ -4104,9 +4091,6 @@ } }, "web/app/components/workflow/block-selector/featured-triggers.tsx": { - "no-restricted-imports": { - "count": 1 - }, "react/set-state-in-effect": { "count": 2 }, @@ -4139,11 +4123,6 @@ "count": 1 } }, - "web/app/components/workflow/block-selector/start-blocks.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/block-selector/tabs.tsx": { "no-restricted-imports": { "count": 1 @@ -4154,11 +4133,6 @@ "count": 1 } }, - "web/app/components/workflow/block-selector/tool/action-item.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx": { "ts/no-explicit-any": { "count": 1 @@ -4170,9 +4144,6 @@ } }, "web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx": { - "no-restricted-imports": { - "count": 1 - }, "ts/no-explicit-any": { "count": 1 } diff --git a/packages/dify-ui/AGENTS.md b/packages/dify-ui/AGENTS.md index 651b117070..4a7fe2f22a 100644 --- a/packages/dify-ui/AGENTS.md +++ b/packages/dify-ui/AGENTS.md @@ -11,6 +11,27 @@ Shared design tokens, the `cn()` utility, a Tailwind CSS preset, and headless pr - Props pattern: `Omit & VariantProps & { /* custom */ }`. - When a component accepts a prop typed from a shared internal module, `export type` it from that component so consumers import it from the component subpath. +## Overlay Primitive Selection: Tooltip vs PreviewCard vs Popover + +Pick by the **trigger's purpose** and **a11y reach**, not visual richness. + +| Primitive | Opens on | Trigger's purpose | Content | Reachable on touch / SR? | +| ------------- | --------------------- | -------------------------- | ------------------------- | ------------------------ | +| `Tooltip` | hover / focus | has its own action | short plain-text label | ❌ (label only) | +| `PreviewCard` | hover / focus | has a primary click target | supplementary preview | ❌ (via click target) | +| `Popover` | click / tap (+ hover) | **to open the popup** | anything, incl. long text | ✅ | + +Base UI decision rule ([docs]): + +> _"If the trigger's purpose is to open the popup itself, it's a popover. +> If the trigger's purpose is unrelated to opening the popup, it's a tooltip."_ + +Apply this first, then narrow: + +- `Tooltip` — ephemeral visual label. Trigger must already carry its own `aria-label` / visible text; tooltip mirrors it for sighted mouse/keyboard users. No interactive UI, no multi-line prose. Not dwell-able. +- `PreviewCard` — hover-revealed rich supplementary preview anchored to a trigger whose click goes somewhere (link, selectable row, jumpable chip). **Hard contract:** the popup MUST NOT contain information or actions unreachable from the trigger's click destination — touch and SR users can't open it. If the info is unique to the popup, switch to `Popover` (click or `openOnHover`) or move it to the click destination. Do not hand-roll "hover to open" on top of `Popover` to evade this split. +- `Popover` — any popup with its own interactions, or any "infotip" (`?` / `(i)` glyph whose sole purpose is to reveal help text). Pass `openOnHover` on `PopoverTrigger` for the infotip case — unlike `Tooltip` / `PreviewCard`, this stays accessible to touch and SR users because the popover still opens on tap and focus. + ## Border Radius: Figma Token → Tailwind Class Mapping The Figma design system uses `--radius/*` tokens whose scale is **offset by one step** from Tailwind CSS v4 defaults. When translating Figma specs to code, always use this mapping — never use `radius-*` as a CSS class, and never extend `borderRadius` in the preset. @@ -34,3 +55,5 @@ The Figma design system uses `--radius/*` tokens whose scale is **offset by one - **Do not** use `radius-*` as CSS class names. The old `@utility radius-*` definitions have been removed. - When the Figma MCP returns `rounded-[var(--radius/sm, 6px)]`, convert it to the standard Tailwind class from the table above (e.g. `rounded-md`). - For values without a standard Tailwind equivalent (10px, 20px, 28px), use arbitrary values like `rounded-[10px]`. + +[docs]: https://base-ui.com/react/components/tooltip#infotips diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json index e1b7a3c1ef..408ba2c432 100644 --- a/packages/dify-ui/package.json +++ b/packages/dify-ui/package.json @@ -49,6 +49,10 @@ "types": "./src/popover/index.tsx", "import": "./src/popover/index.tsx" }, + "./preview-card": { + "types": "./src/preview-card/index.tsx", + "import": "./src/preview-card/index.tsx" + }, "./scroll-area": { "types": "./src/scroll-area/index.tsx", "import": "./src/scroll-area/index.tsx" diff --git a/packages/dify-ui/src/popover/index.stories.tsx b/packages/dify-ui/src/popover/index.stories.tsx index dcea5018ab..802337634b 100644 --- a/packages/dify-ui/src/popover/index.stories.tsx +++ b/packages/dify-ui/src/popover/index.stories.tsx @@ -20,7 +20,7 @@ const meta = { layout: 'centered', docs: { description: { - component: 'Compound popover built on Base UI Popover. Use it for contextual affordances, overflow menus, filters, and forms that anchor to a trigger. Control placement via the `placement` prop on `PopoverContent` and compose arbitrary children inside the popup.', + component: 'Compound popover built on Base UI Popover. Use it for contextual affordances, overflow menus, filters, and forms that anchor to a trigger. Control placement via the `placement` prop on `PopoverContent` and compose arbitrary children inside the popup.\n\nPass `openOnHover` on `PopoverTrigger` when the popup should also reveal on hover (see the **Infotip** story). Unlike `Tooltip` and `PreviewCard`, hover on `Popover` still falls back to tap/focus, so touch and screen-reader users can reach the content.', }, }, }, @@ -101,6 +101,48 @@ export const WithActions: Story = { ), } +export const Infotip: Story = { + parameters: { + docs: { + description: { + story: [ + 'The **infotip** pattern from [Base UI](https://base-ui.com/react/components/tooltip#infotips): an info glyph (`?`, `(i)`) whose sole purpose is to reveal explanatory text. Use `Popover` with `openOnHover` on the trigger — never `Tooltip`.', + '', + 'Why not `Tooltip`? Tooltips are disabled on touch devices and not announced to screen readers; descriptive help text hidden in them is unreachable for those users. Why not `PreviewCard`? PreviewCard\'s a11y contract requires the trigger to already own a primary click destination, but an info glyph has no other purpose.', + '', + 'Base UI rule of thumb: *"If the trigger\'s purpose is to open the popup itself, it\'s a popover. If the trigger\'s purpose is unrelated to opening the popup, it\'s a tooltip."*', + '', + 'Hover, tap, or focus the `?` icon to open. In the Dify app, reach for `@/app/components/base/infotip` (`{helpText}`) which wraps this pattern with consistent delays (300/200), typography, and `aria-label` plumbing.', + ].join('\n'), + }, + }, + }, + render: () => ( +
+ Usage priority + + + + + )} + /> + + Set which resource to use first when running models. The Trial quota will be used after the paid quota is exhausted. + + +
+ ), +} + const PLACEMENTS: Placement[] = [ 'top-start', 'top', diff --git a/packages/dify-ui/src/preview-card/__tests__/index.spec.tsx b/packages/dify-ui/src/preview-card/__tests__/index.spec.tsx new file mode 100644 index 0000000000..5d1e325051 --- /dev/null +++ b/packages/dify-ui/src/preview-card/__tests__/index.spec.tsx @@ -0,0 +1,127 @@ +import { render } from 'vitest-browser-react' +import { + PreviewCard, + PreviewCardContent, + PreviewCardTrigger, +} from '..' + +const renderWithSafeViewport = (ui: import('react').ReactNode) => render( +
+ {ui} +
, +) + +describe('PreviewCardContent', () => { + describe('Placement', () => { + it('should use bottom placement and default offsets when placement props are not provided', async () => { + const screen = await renderWithSafeViewport( + + Open} + /> + + Default content + + , + ) + + await expect.element(screen.getByRole('group', { name: 'default positioner' })).toHaveAttribute('data-side', 'bottom') + await expect.element(screen.getByRole('group', { name: 'default positioner' })).toHaveAttribute('data-align', 'center') + await expect.element(screen.getByRole('dialog', { name: 'default popup' })).toHaveTextContent('Default content') + }) + + it('should apply parsed custom placement and custom offsets when placement props are provided', async () => { + const screen = await renderWithSafeViewport( + + Open} + /> + + Custom placement content + + , + ) + + await expect.element(screen.getByRole('group', { name: 'custom positioner' })).toHaveAttribute('data-side', 'top') + await expect.element(screen.getByRole('group', { name: 'custom positioner' })).toHaveAttribute('data-align', 'end') + await expect.element(screen.getByRole('dialog', { name: 'custom popup' })).toHaveTextContent('Custom placement content') + }) + }) + + describe('Passthrough props', () => { + it('should forward positionerProps and popupProps when passthrough props are provided', async () => { + const onPopupClick = vi.fn() + + const screen = await render( + + Open} + /> + + Preview body + + , + ) + + const popup = screen.getByRole('dialog', { name: 'preview content' }) + await popup.click() + + await expect.element(screen.getByRole('group', { name: 'preview positioner' })).toHaveAttribute('id', 'preview-positioner-id') + await expect.element(popup).toHaveAttribute('id', 'preview-popup-id') + expect(onPopupClick).toHaveBeenCalledTimes(1) + }) + }) + + describe('Trigger click behavior', () => { + it('should forward the trigger click to the consumer handler so the primary action runs', async () => { + const onPrimaryClick = vi.fn() + + const screen = await renderWithSafeViewport( + + + Open + + )} + /> + + Preview body + + , + ) + + const trigger = screen.getByRole('button', { name: 'preview trigger' }) + await trigger.click() + + expect(onPrimaryClick).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/packages/dify-ui/src/preview-card/index.stories.tsx b/packages/dify-ui/src/preview-card/index.stories.tsx new file mode 100644 index 0000000000..540ac08c1a --- /dev/null +++ b/packages/dify-ui/src/preview-card/index.stories.tsx @@ -0,0 +1,213 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import type { Placement } from '.' +import { useState } from 'react' +import { + createPreviewCardHandle, + PreviewCard, + PreviewCardContent, + PreviewCardTrigger, +} from '.' + +const rowButtonClassName + = 'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-text-secondary hover:bg-state-base-hover' + +const triggerButtonClassName + = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover' + +const inlineLinkClassName + = 'text-text-accent underline decoration-text-accent/60 decoration-1 underline-offset-2 outline-hidden hover:decoration-text-accent focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-text-accent data-[popup-open]:decoration-text-accent' + +const meta = { + title: 'Base/UI/PreviewCard', + component: PreviewCard, + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'Hover- and focus-activated rich preview for triggers whose primary click has its own destination (following a link, selecting a row, jumping to a definition). Built on Base UI PreviewCard.\n\n**A11y contract:** touch and screen-reader users cannot open the preview. Never place information or actions in the popup that are not also reachable from the trigger\'s primary click destination. If that is unavoidable, add a separate click affordance (Popover) or move the unique content onto the destination.', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +// --- Canonical: inline link preview --------------------------------------- +// Mirrors Base UI's own PreviewCard docs demo: an inline `` in a +// paragraph, hovering reveals a rich preview (image + summary) of the link's +// destination. The Wikipedia URL and Unsplash image are the exact assets used +// in base-ui.com's public docs so the story renders a real preview. +// https://base-ui.com/react/components/preview-card +const typographyPreview = createPreviewCardHandle() + +export const LinkPreview: Story = { + name: 'Link preview (canonical)', + parameters: { + docs: { + description: { + story: + 'The prototypical PreviewCard use case: an inline hyperlink with a rich hover preview of the destination. Uses a detached trigger + `createPreviewCardHandle()` so the trigger can sit inline in prose while the popup content is defined elsewhere. The trigger renders a real `` — click still follows the link; the preview is strictly supplementary.', + }, + }, + }, + render: () => ( +
+

+ The principles of good + {' '} + + typography + + {' '} + remain in the digital age. +

+ + + +
+ Station Hofplein signage in Rotterdam, Netherlands +

+ Typography + {' '} + is the art and science of arranging type to make written language legible, readable, and visually appealing. +

+
+
+
+
+ ), +} + +export const Supplementary: Story = { + name: 'Supplementary preview on a button trigger', + parameters: { + docs: { + description: { + story: + 'Application-level adaptation of the same semantic: the trigger is a `
+ )} + /> + +
+
gpt-4o
+
+ Multimodal flagship model. Vision, audio and 128k context. +
+
+
+ + ), +} + +const PLACEMENTS: Placement[] = [ + 'top-start', + 'top', + 'top-end', + 'right-start', + 'right', + 'right-end', + 'bottom-start', + 'bottom', + 'bottom-end', + 'left-start', + 'left', + 'left-end', +] + +const PlacementsDemo = () => { + const [placement, setPlacement] = useState('bottom') + + return ( +
+
+ {PLACEMENTS.map(value => ( + + ))} +
+ + Hover me} + /> + +
+
+ placement=" + {placement} + " +
+
+ Preview positions itself relative to the trigger. +
+
+
+
+
+ ) +} + +export const Placements: Story = { + parameters: { + layout: 'fullscreen', + }, + render: () => , +} + +const CustomDelayDemo = () => ( + + Snappy trigger} + /> + +
+
Fast hover
+
+ Base UI defaults (600ms / 300ms) are tuned for link previews. Override per trigger for denser UIs. +
+
+
+
+) + +export const CustomDelays: Story = { + render: () => , +} diff --git a/packages/dify-ui/src/preview-card/index.tsx b/packages/dify-ui/src/preview-card/index.tsx new file mode 100644 index 0000000000..771b15cf13 --- /dev/null +++ b/packages/dify-ui/src/preview-card/index.tsx @@ -0,0 +1,81 @@ +'use client' + +import type { ReactNode } from 'react' +import type { Placement } from '../placement' +import { PreviewCard as BasePreviewCard } from '@base-ui/react/preview-card' +import { cn } from '../cn' +import { parsePlacement } from '../placement' + +export type { Placement } + +/** + * PreviewCard is a hover/focus-triggered rich preview intended to supplement a + * trigger whose primary action is its own click destination (e.g. a link, a + * selectable row, a chip that jumps to a definition). + * + * A11y contract — match Base UI's guidance: + * - The popup MUST NOT contain information or actions that are not also + * reachable from the trigger's primary click destination. Touch and screen + * reader users cannot open the card and must be able to get the same + * information/actions without it. + * - If content is unique to the popup, either (a) add a separate click-triggered + * affordance (Popover) next to the trigger, or (b) move the unique content + * onto the click destination. + */ +export const PreviewCard = BasePreviewCard.Root +export const PreviewCardTrigger = BasePreviewCard.Trigger +export const createPreviewCardHandle = BasePreviewCard.createHandle + +type PreviewCardContentProps = { + children: ReactNode + placement?: Placement + sideOffset?: number + alignOffset?: number + className?: string + popupClassName?: string + positionerProps?: Omit< + BasePreviewCard.Positioner.Props, + 'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset' + > + popupProps?: Omit< + BasePreviewCard.Popup.Props, + 'children' | 'className' + > +} + +export function PreviewCardContent({ + children, + placement = 'bottom', + sideOffset = 8, + alignOffset = 0, + className, + popupClassName, + positionerProps, + popupProps, +}: PreviewCardContentProps) { + const { side, align } = parsePlacement(placement) + + return ( + + + + {children} + + + + ) +} diff --git a/packages/dify-ui/src/tooltip/__tests__/index.spec.tsx b/packages/dify-ui/src/tooltip/__tests__/index.spec.tsx index 3660c2c8e5..043835f697 100644 --- a/packages/dify-ui/src/tooltip/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/tooltip/__tests__/index.spec.tsx @@ -46,20 +46,7 @@ describe('TooltipContent', () => { }) }) - describe('Variant and popup props', () => { - it('should render popup content when variant is plain', async () => { - const screen = await render( - - Trigger - - Plain tooltip body - - , - ) - - await expect.element(screen.getByRole('tooltip', { name: 'plain tooltip' })).toHaveTextContent('Plain tooltip body') - }) - + describe('Popup props', () => { it('should forward popup props and handlers when popup props are provided', async () => { const onMouseEnter = vi.fn() @@ -83,7 +70,11 @@ describe('TooltipContent', () => { await expect.element(popup).toHaveAttribute('id', 'tooltip-popup-id') await expect.element(popup).toHaveAttribute('data-track-id', 'tooltip-track') - expect(onMouseEnter).toHaveBeenCalledTimes(1) + // Intent of the assertion is "handler is wired up". The exact call count + // depends on vitest-browser's pointer simulation and Base UI's internal + // pointer tracking (both of which may fire more than one enter event for + // a single `.hover()` action), so assert presence, not count. + expect(onMouseEnter).toHaveBeenCalled() }) it('should apply className to the popup and positionerClassName to the positioner', async () => { diff --git a/packages/dify-ui/src/tooltip/index.stories.tsx b/packages/dify-ui/src/tooltip/index.stories.tsx index dca3be32f3..902449d4a4 100644 --- a/packages/dify-ui/src/tooltip/index.stories.tsx +++ b/packages/dify-ui/src/tooltip/index.stories.tsx @@ -8,8 +8,8 @@ import { TooltipTrigger, } from '.' -const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover' const iconButtonClassName = 'inline-flex h-8 w-8 items-center justify-center rounded-lg border border-divider-subtle bg-components-button-secondary-bg text-text-secondary shadow-xs hover:bg-state-base-hover' +const triggerButtonClassName = 'rounded-lg border border-divider-subtle bg-components-button-secondary-bg px-3 py-1.5 text-sm text-text-secondary shadow-xs hover:bg-state-base-hover' const meta = { title: 'Base/UI/Tooltip', @@ -25,7 +25,7 @@ const meta = { layout: 'centered', docs: { description: { - component: 'Compound tooltip built on Base UI Tooltip. Wrap the app in `TooltipProvider` (done automatically in these stories) so multiple tooltips share open/close delays. Each tooltip pairs a `TooltipTrigger` with a `TooltipContent` and supports placement, offsets, and two style variants.', + component: 'Compound tooltip built on Base UI Tooltip. Wrap the app in `TooltipProvider` (done automatically in these stories) so multiple tooltips share open/close delays. Each tooltip pairs a `TooltipTrigger` with a `TooltipContent` and supports placement and offsets.\n\n**Usage contract** (mirrors the [Base UI tooltip guidelines](https://base-ui.com/react/components/tooltip#alternatives-to-tooltips)):\n\n- Tooltips are **supplementary visual labels** for sighted mouse and keyboard users. They are disabled on touch devices and are not announced to screen readers.\n- The trigger **must carry its own `aria-label` or visible text** that matches the tooltip — the tooltip does not replace labeling.\n- Keep content short and non-interactive (an icon-button label, a keyboard shortcut, one-word clarification).\n- **Do not** place descriptions, prose, links, or interactive controls inside a tooltip — touch and screen-reader users cannot reach them.\n- For hover-triggered rich previews that users move their cursor onto, use `PreviewCard` (dwell-able, structured content).\n- For an info icon that explains a concept (an "infotip"), or for any hover popup that needs interactive content or to reach touch/assistive-tech users, use `Popover` with `openOnHover` on the trigger.', }, }, }, @@ -35,47 +35,58 @@ const meta = { export default meta type Story = StoryObj -export const Default: Story = { - render: () => ( - - } - > - Hover me - - - Tooltips describe interactive elements without a click. - - - ), -} +const ICON_ACTIONS = [ + { icon: 'i-ri-pencil-line', label: 'Edit' }, + { icon: 'i-ri-file-copy-line', label: 'Duplicate' }, + { icon: 'i-ri-archive-line', label: 'Archive' }, + { icon: 'i-ri-delete-bin-line', label: 'Delete' }, +] as const -export const Plain: Story = { +export const IconButton: Story = { + name: 'Icon button (canonical)', parameters: { docs: { description: { - story: 'Use `variant="plain"` to render the popup without default chrome (background, padding, typography). Apply your own styling via `className` on `TooltipContent`.', + story: 'The canonical tooltip use case: an icon-only button surfaces its accessible label as a tooltip for sighted mouse and keyboard users. The trigger already carries `aria-label` — the tooltip mirrors that label visually; it does **not** replace it.', + }, + }, + }, + render: () => ( +
+ {ICON_ACTIONS.map(({ icon, label }) => ( + + + + + )} + /> + {label} + + ))} +
+ ), +} + +export const KeyboardShortcut: Story = { + parameters: { + docs: { + description: { + story: 'A short, supplementary hint that surfaces a keyboard shortcut next to a visible button label. The trigger is fully self-describing ("Save"); the tooltip only adds non-essential extra clarity for mouse/keyboard users.', }, }, }, render: () => ( } - > - Preview details - - -
- Dataset preview - - 32 documents • Last indexed 2 minutes ago - -
-
+ render={( + + )} + /> + ⌘S
), } @@ -116,14 +127,10 @@ const PlacementsDemo = () => {
} - > - Anchor - + render={} + /> - placement=" - {placement} - " + {`placement="${placement}"`} @@ -133,113 +140,45 @@ const PlacementsDemo = () => { export const Placements: Story = { parameters: { layout: 'fullscreen', + docs: { + description: { + story: 'Placement reference. `placement` accepts the 12 standard side/align combinations; Base UI flips automatically if the tooltip would overflow the viewport.', + }, + }, }, render: () => , } -export const OnIconButtons: Story = { - parameters: { - docs: { - description: { - story: 'Tooltips are essential for icon-only buttons. The trigger is the button; the tooltip provides the accessible label and hover hint.', - }, - }, - }, - render: () => ( -
- - - - - )} - /> - Edit - - - - - - )} - /> - Duplicate - - - - - - )} - /> - Archive - - - - - - )} - /> - Delete - -
- ), -} - -export const LongContent: Story = { - render: () => ( - - } - > - What are tokens? - - - Tokens are the basic units a model reads. English text averages ~4 characters per token; non-Latin scripts often use more tokens per character. Both input and output count toward your quota. - - - ), -} - const DELAY_PRESETS: Array<{ label: string, delay: number }> = [ - { label: 'Instant (0ms)', delay: 0 }, - { label: 'Fast (150ms)', delay: 150 }, - { label: 'Default (600ms)', delay: 600 }, + { label: 'Instant', delay: 0 }, + { label: 'Fast', delay: 150 }, + { label: 'Default', delay: 600 }, ] -const DelayDemo = () => { - return ( -
- {DELAY_PRESETS.map(({ label, delay }) => ( - - - } - > - {label} - - - Appeared after - {delay} - ms hover delay. - - - - ))} -
- ) -} +const DelayDemo = () => ( +
+ {DELAY_PRESETS.map(({ label, delay }) => ( + + + + + + )} + /> + {`${label} (${delay}ms)`} + + + ))} +
+) export const WithDelay: Story = { parameters: { docs: { description: { - story: '`TooltipProvider` controls hover `delay` (and `closeDelay`) for the tooltips nested inside it. Adjacent tooltips under the same provider open instantly after the first has been shown.', + story: '`TooltipProvider` controls hover `delay` (and `closeDelay`) for the tooltips nested inside it. Adjacent tooltips under the same provider open instantly after the first has been shown. The Dify app root sets `delay={300} closeDelay={200}` — override locally only when the surrounding UX demands it.', }, }, }, diff --git a/packages/dify-ui/src/tooltip/index.tsx b/packages/dify-ui/src/tooltip/index.tsx index e0fcd7c5c3..1f9772ce2d 100644 --- a/packages/dify-ui/src/tooltip/index.tsx +++ b/packages/dify-ui/src/tooltip/index.tsx @@ -8,7 +8,28 @@ import { parsePlacement } from '../placement' export type { Placement } -type TooltipContentVariant = 'default' | 'plain' +/** + * Tooltip is an **ephemeral hint** tied to a trigger (typically an icon button, + * badge, or short label). It follows Base UI's Tooltip semantics: + * + * - Opens on pointer hover or keyboard focus on the trigger. + * - Closes as soon as the pointer leaves the trigger — the popup itself is + * **not dwell-able**; users cannot move their cursor onto the tooltip. + * - Must contain only short, non-interactive text. No links, buttons, form + * controls, or structured panels. + * + * If you need any of the following, use `PreviewCard` instead (hover-triggered + * rich preview that users can move their cursor onto): + * + * - Multi-line or structured content (icon + title + metadata) + * - Content the user needs to "stop and read" for more than ~1 second + * - Content wider than ~300px + * + * If you need interactive affordances (buttons, links, forms) use `Popover`. + */ +export const TooltipProvider = BaseTooltip.Provider +export const Tooltip = BaseTooltip.Root +export const TooltipTrigger = BaseTooltip.Trigger type TooltipContentProps = { children: ReactNode @@ -17,7 +38,6 @@ type TooltipContentProps = { alignOffset?: number positionerClassName?: string className?: string - variant?: TooltipContentVariant } & Omit export function TooltipContent({ @@ -27,7 +47,6 @@ export function TooltipContent({ alignOffset = 0, positionerClassName, className, - variant = 'default', ...props }: TooltipContentProps) { const { side, align } = parsePlacement(placement) @@ -43,7 +62,7 @@ export function TooltipContent({ > ) } - -export const TooltipProvider = BaseTooltip.Provider -export const Tooltip = BaseTooltip.Root -export const TooltipTrigger = BaseTooltip.Trigger diff --git a/web/app/components/base/infotip/index.tsx b/web/app/components/base/infotip/index.tsx new file mode 100644 index 0000000000..b97b499af3 --- /dev/null +++ b/web/app/components/base/infotip/index.tsx @@ -0,0 +1,82 @@ +'use client' + +import type { Placement } from '@langgenius/dify-ui/popover' +import type { ReactNode } from 'react' +import { cn } from '@langgenius/dify-ui/cn' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' + +/** + * Infotip — a `?` icon that reveals a long-form explanation on hover / focus / tap. + * + * Implements the pattern Base UI calls an "infotip": + * https://base-ui.com/react/components/tooltip#infotips + * + * > "Popups that open when hovering an info icon should use Popover with the + * > `openOnHover` prop on the trigger instead of a tooltip. This way, touch + * > users and screen reader users can access the content." + * + * Use whenever the trigger is an info glyph whose sole purpose is to open a + * popup (help text, documentation-style explanation). Do NOT use `Tooltip` for + * this — Tooltip is reserved for ephemeral, non-interactive visual labels that + * are unreachable on touch devices and by screen readers. + * + * Base UI rule of thumb: + * + * > "If the trigger's purpose is to open the popup itself, it's a popover. + * > If the trigger's purpose is unrelated to opening the popup, it's a tooltip." + * + * For hover-revealed supplementary previews of a link / row trigger that has + * its own primary click destination, use `PreviewCard` instead. + */ + +type InfotipProps = { + /** Popup content. Rich nodes are allowed. */ + 'children': ReactNode + /** Accessible name for the trigger. Required; should match the popup text. */ + 'aria-label': string + /** Placement of the popup relative to the trigger. Defaults to `top`. */ + 'placement'?: Placement + /** Extra classes on the outer trigger wrapper (layout / margin). */ + 'className'?: string + /** Extra classes on the `?` icon itself (size / color overrides). */ + 'iconClassName'?: string + /** Extra classes on the popup body (width / padding / whitespace overrides). */ + 'popupClassName'?: string + /** Hover open delay in ms. Defaults to 300 to match the app-wide Tooltip delay. */ + 'delay'?: number + /** Hover close delay in ms. Defaults to 200 to match the app-wide Tooltip delay. */ + 'closeDelay'?: number +} + +export function Infotip({ + children, + 'aria-label': ariaLabel, + placement = 'top', + className, + iconClassName, + popupClassName, + delay = 300, + closeDelay = 200, +}: InfotipProps) { + return ( + + + + + )} + /> + + {children} + + + ) +} diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx index db3e474b60..b0338cb823 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx @@ -302,8 +302,8 @@ describe('HITLInputVariableBlockComponent', () => { }) }) - describe('Tooltip payload', () => { - it('should call getVarType with rag selector and use rag node id mapping', () => { + describe('Full-path preview payload', () => { + it('should resolve the rag node via isRagVar offset and skip the full-path preview', () => { const getVarType = vi.fn(() => Type.number) const { container } = renderVariableBlock({ variables: ['rag', 'node-rag', 'chunk'], @@ -314,10 +314,9 @@ describe('HITLInputVariableBlockComponent', () => { expect(screen.getByText('chunk')).toBeInTheDocument() expect(hasErrorIcon(container)).toBe(false) - expect(getVarType).toHaveBeenCalledWith({ - nodeId: 'rag', - valueSelector: ['rag', 'node-rag', 'chunk'], - }) + // Rag selectors always have `isShowAPart === false`, so the full-path + // preview is not rendered and `getVarType` is not invoked. + expect(getVarType).not.toHaveBeenCalled() }) it('should use shortened display name for deep non-rag selectors', () => { diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx index 44303c8faa..913d5039c1 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx @@ -1,6 +1,7 @@ import type { UpdateWorkflowNodesMapPayload } from '../workflow-variable-block' import type { WorkflowNodesMap } from '../workflow-variable-block/node' import type { ValueSelector, Var } from '@/app/components/workflow/types' +import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' import { @@ -13,7 +14,6 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' import { isConversationVar, isENV, @@ -119,13 +119,13 @@ const HITLInputVariableBlockComponent = ({ /> ) - if (!node) + if (!node || !isShowAPart) return Item return ( - + {Item}} /> + - )} - disabled={!isShowAPart} - > -
{Item}
-
+ + ) } diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx index ecd5183a14..cb9178f36b 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -3,7 +3,7 @@ import type { } from './index' import type { WorkflowNodesMap } from './node' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' -import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' +import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' import { @@ -151,13 +151,13 @@ const WorkflowVariableBlockComponent = ({ /> ) - if (!node) + if (!node || !isShowAPart) return Item return ( - - {Item}} /> - + + {Item}} /> + - - + + ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx index f7e1962fd7..733b299664 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item.tsx @@ -7,9 +7,9 @@ import { cn } from '@langgenius/dify-ui/cn' import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger, SelectValue } from '@langgenius/dify-ui/select' import { Slider } from '@langgenius/dify-ui/slider' import { Switch } from '@langgenius/dify-ui/switch' -import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { Infotip } from '@/app/components/base/infotip' import PromptEditor from '@/app/components/base/prompt-editor' import Radio from '@/app/components/base/radio' import TagInput from '@/app/components/base/tag-input' @@ -349,18 +349,13 @@ function ParameterItem({ { parameterRule.help && ( - - - - - )} - /> - -
{parameterRule.help[language] || parameterRule.help.en_US}
-
-
+ + {parameterRule.help[language] || parameterRule.help.en_US} + ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx index ed16cd7904..72c52a9429 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx @@ -10,7 +10,11 @@ import { PopoverContent, PopoverTrigger, } from '@langgenius/dify-ui/popover' -import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' +import { + PreviewCard, + PreviewCardContent, + PreviewCardTrigger, +} from '@langgenius/dify-ui/preview-card' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' @@ -160,8 +164,13 @@ const PopupItem: FC = ({ {!collapsed && model.models.map(modelItem => ( - - + = ({ )} /> -
@@ -245,8 +253,8 @@ const PopupItem: FC = ({
)}
-
-
+ + ))} ) diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx index ec07b6c114..47ddb55b6c 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx @@ -225,7 +225,7 @@ const Popup: FC = ({ {showCreditsExhaustedAlert && ( )} -
+
{ filteredModelList.map(model => ( @@ -31,19 +32,9 @@ export default function UsagePrioritySection({ value, disabled, onSelect }: Usag {t('modelProvider.card.usagePriority', { ns: 'common' })} - - - - - )} - /> - - {t('modelProvider.card.usagePriorityTip', { ns: 'common' })} - - + + {usagePriorityTip} +
{options.map(option => ( diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx index bbc2cfaa98..8de8167dc2 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx @@ -9,6 +9,7 @@ import { useBoolean } from 'ahooks' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { Infotip } from '@/app/components/base/infotip' import Loading from '@/app/components/base/loading' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import useTimestamp from '@/hooks/use-timestamp' @@ -100,19 +101,9 @@ const QuotaPanel: FC = ({
{t('modelProvider.quota', { ns: 'common' })} - - - - - )} - /> - - {tipText} - - + + {tipText} +
diff --git a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx index 9f993260ca..8ceaaaa32e 100644 --- a/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/system-model-selector/index.tsx @@ -11,13 +11,9 @@ import { DialogTitle, } from '@langgenius/dify-ui/dialog' import { toast } from '@langgenius/dify-ui/toast' -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@langgenius/dify-ui/tooltip' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { Infotip } from '@/app/components/base/infotip' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { updateDefaultModel } from '@/service/common' @@ -138,21 +134,13 @@ const SystemModel: FC = ({ return (
{t(labelKey, { ns: 'common' })} - - - - - )} - /> - -
- {tipText} -
-
-
+ + {tipText} +
) } diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 7179a7c458..8258e4d450 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -1,5 +1,10 @@ import type { NodeDefault } from '../types' import type { BlockClassificationEnum } from './types' +import { + PreviewCard, + PreviewCardContent, + PreviewCardTrigger, +} from '@langgenius/dify-ui/preview-card' import { groupBy } from 'es-toolkit/compat' import { memo, @@ -9,7 +14,6 @@ import { import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import Badge from '@/app/components/base/badge' -import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '../block-icon' import { BlockEnum } from '../types' import { BLOCK_CLASSIFICATIONS } from './constants' @@ -92,13 +96,40 @@ const Blocks = ({ ) } { + // Preview is supplementary: icon/title/description are all reachable + // from the node that gets added on click (inspector + canvas), so + // hover/focus-only activation is a11y-safe. See + // packages/dify-ui/AGENTS.md → Overlay Primitive Selection. filteredList.map(block => ( - + onSelect(block.metaData.type)} + > + +
{block.metaData.title}
+ { + block.metaData.type === BlockEnum.LoopEnd && ( + + ) + } +
+ )} + /> +
{block.metaData.title}
{block.metaData.description}
- )} - > -
onSelect(block.metaData.type)} - > - -
{block.metaData.title}
- { - block.metaData.type === BlockEnum.LoopEnd && ( - - ) - } -
- + + )) }
diff --git a/web/app/components/workflow/block-selector/featured-tools.tsx b/web/app/components/workflow/block-selector/featured-tools.tsx index 68c6d63a52..0cdeebcb79 100644 --- a/web/app/components/workflow/block-selector/featured-tools.tsx +++ b/web/app/components/workflow/block-selector/featured-tools.tsx @@ -3,12 +3,12 @@ import type { ToolWithProvider } from '../types' import type { ToolDefaultValue, ToolValue } from './types' import type { Plugin } from '@/app/components/plugins/types' import type { Locale } from '@/i18n-config' +import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import { RiMoreLine } from '@remixicon/react' import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows' import Loading from '@/app/components/base/loading' -import Tooltip from '@/app/components/base/tooltip' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' import { useGetLanguage } from '@/context/i18n' @@ -235,7 +235,6 @@ function FeaturedToolUninstalledItem({ const description = typeof plugin.brief === 'object' ? plugin.brief[language] : plugin.brief const installCountLabel = t('install', { ns: 'plugin', num: formatNumber(plugin.install_count || 0) }) const [actionOpen, setActionOpen] = useState(false) - const [isActionHovered, setIsActionHovered] = useState(false) const [isInstallModalOpen, setIsInstallModalOpen] = useState(false) useEffect(() => { @@ -244,7 +243,6 @@ function FeaturedToolUninstalledItem({ const handleScroll = () => { setActionOpen(false) - setIsActionHovered(false) } window.addEventListener('scroll', handleScroll, true) @@ -254,77 +252,72 @@ function FeaturedToolUninstalledItem({ } }, [actionOpen]) + const row = ( +
+
+ +
+
{label}
+
+
+
+ {installCountLabel} +
+ + +
+
+
+ ) + return ( <> - - -
{label}
-
{description}
-
- )} - disabled={!description || isActionHovered || actionOpen || isInstallModalOpen} - > -
-
- -
-
{label}
-
-
-
- {installCountLabel} -
setIsActionHovered(true)} - onMouseLeave={() => { - if (!actionOpen) - setIsActionHovered(false) - }} - > - - { - setActionOpen(value) - setIsActionHovered(value) - }} - author={plugin.org} - name={plugin.name} - version={plugin.latest_version} - /> -
-
-
- + {description + ? ( + // Preview is supplementary: icon / label / brief are all reachable from + // the InstallFromMarketplace modal that opens on click, so hover/focus-only + // activation is a11y-safe. See packages/dify-ui/AGENTS.md → Overlay Primitive Selection. + + + +
+ +
{label}
+
{description}
+
+
+
+ ) + : row} {isInstallModalOpen && ( { setIsInstallModalOpen(false) - setIsActionHovered(false) await onInstallSuccess?.() }} onClose={() => { setIsInstallModalOpen(false) - setIsActionHovered(false) }} /> )} diff --git a/web/app/components/workflow/block-selector/featured-triggers.tsx b/web/app/components/workflow/block-selector/featured-triggers.tsx index c7c83410a6..3d3cdee2b7 100644 --- a/web/app/components/workflow/block-selector/featured-triggers.tsx +++ b/web/app/components/workflow/block-selector/featured-triggers.tsx @@ -2,12 +2,12 @@ import type { TriggerDefaultValue, TriggerWithProvider } from './types' import type { Plugin } from '@/app/components/plugins/types' import type { Locale } from '@/i18n-config' +import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import { RiMoreLine } from '@remixicon/react' import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows' import Loading from '@/app/components/base/loading' -import Tooltip from '@/app/components/base/tooltip' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' import { useGetLanguage } from '@/context/i18n' @@ -230,7 +230,6 @@ function FeaturedTriggerUninstalledItem({ const description = typeof plugin.brief === 'object' ? plugin.brief[language] : plugin.brief const installCountLabel = t('install', { ns: 'plugin', num: formatNumber(plugin.install_count || 0) }) const [actionOpen, setActionOpen] = useState(false) - const [isActionHovered, setIsActionHovered] = useState(false) const [isInstallModalOpen, setIsInstallModalOpen] = useState(false) useEffect(() => { @@ -239,7 +238,6 @@ function FeaturedTriggerUninstalledItem({ const handleScroll = () => { setActionOpen(false) - setIsActionHovered(false) } window.addEventListener('scroll', handleScroll, true) @@ -249,77 +247,72 @@ function FeaturedTriggerUninstalledItem({ } }, [actionOpen]) + const row = ( +
+
+ +
+
{label}
+
+
+
+ {installCountLabel} +
+ + +
+
+
+ ) + return ( <> - - -
{label}
-
{description}
-
- )} - disabled={!description || isActionHovered || actionOpen || isInstallModalOpen} - > -
-
- -
-
{label}
-
-
-
- {installCountLabel} -
setIsActionHovered(true)} - onMouseLeave={() => { - if (!actionOpen) - setIsActionHovered(false) - }} - > - - { - setActionOpen(value) - setIsActionHovered(value) - }} - author={plugin.org} - name={plugin.name} - version={plugin.latest_version} - /> -
-
-
- + {description + ? ( + // Preview is supplementary: icon / label / brief are all reachable from + // the InstallFromMarketplace modal that opens on click, so hover/focus-only + // activation is a11y-safe. See packages/dify-ui/AGENTS.md → Overlay Primitive Selection. + + + +
+ +
{label}
+
{description}
+
+
+
+ ) + : row} {isInstallModalOpen && ( { setIsInstallModalOpen(false) - setIsActionHovered(false) await onInstallSuccess?.() }} onClose={() => { setIsInstallModalOpen(false) - setIsActionHovered(false) }} /> )} diff --git a/web/app/components/workflow/block-selector/start-blocks.tsx b/web/app/components/workflow/block-selector/start-blocks.tsx index ef332844d5..efc1e5c1b9 100644 --- a/web/app/components/workflow/block-selector/start-blocks.tsx +++ b/web/app/components/workflow/block-selector/start-blocks.tsx @@ -1,5 +1,10 @@ import type { BlockEnum, CommonNodeType } from '../types' import type { TriggerDefaultValue } from './types' +import { + PreviewCard, + PreviewCardContent, + PreviewCardTrigger, +} from '@langgenius/dify-ui/preview-card' import { memo, useCallback, @@ -7,9 +12,7 @@ import { useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' import useNodes from '@/app/components/workflow/store/workflow/use-nodes' -import { useAvailableNodesMetaData } from '../../workflow-app/hooks' import BlockIcon from '../block-icon' import { BlockEnum as BlockEnumValues } from '../types' // import { useNodeMetaData } from '../hooks' @@ -33,7 +36,6 @@ const StartBlocks = ({ const { t } = useTranslation() const nodes = useNodes() // const nodeMetaData = useNodeMetaData() - const availableNodesMetaData = useAvailableNodesMetaData() const filteredBlocks = useMemo(() => { // Check if Start node already exists in workflow @@ -67,13 +69,34 @@ const StartBlocks = ({ onContentStateChange?.(!isEmpty) }, [isEmpty, onContentStateChange]) + // Preview is supplementary: the block icon, title and description all become + // reachable from the inspector + canvas once the row is clicked to insert + // the start node, so hover/focus-only activation is a11y-safe. See + // packages/dify-ui/AGENTS.md → Overlay Primitive Selection. const renderBlock = useCallback((block: typeof START_BLOCKS[number]) => ( - + onSelect(block.type)} + > + +
+ {t(`blocks.${block.type}`, { ns: 'workflow' })} + {block.type === BlockEnumValues.Start && ( + {t('blocks.originalStartNode', { ns: 'workflow' })} + )} +
+ + )} + /> +
)}
- )} - > -
onSelect(block.type)} - > - -
- {t(`blocks.${block.type}`, { ns: 'workflow' })} - {block.type === BlockEnumValues.Start && ( - {t('blocks.originalStartNode', { ns: 'workflow' })} - )} -
-
-
- ), [availableNodesMetaData, onSelect, t]) + + + ), [onSelect, t]) if (isEmpty) return null diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx index 4afe1b1da3..343f8482df 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -4,11 +4,11 @@ import type { ToolWithProvider } from '../../types' import type { ToolDefaultValue } from '../types' import type { Tool } from '@/app/components/tools/types' import { cn } from '@langgenius/dify-ui/cn' +import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import * as React from 'react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' -import Tooltip from '@/app/components/base/tooltip' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' import { Theme } from '@/types/app' @@ -57,13 +57,59 @@ const ToolItem: FC = ({ return normalizedIcon }, [theme, normalizedIcon, normalizedIconDark]) - return ( - { + if (disabled) + return + const params: Record = {} + if (payload.parameters) { + payload.parameters.forEach((item) => { + params[item.name] = '' + }) + } + onSelect(BlockEnum.Tool, { + provider_id: provider.id, + provider_type: provider.type, + provider_name: provider.name, + plugin_id: provider.plugin_id, + plugin_unique_identifier: provider.plugin_unique_identifier, + provider_icon: normalizedIcon, + provider_icon_dark: normalizedIconDark, + tool_name: payload.name, + tool_label: payload.label[language]!, + tool_description: payload.description[language], + title: payload.label[language]!, + is_team_authorization: provider.is_team_authorization, + paramSchemas: payload.parameters, + params, + meta: provider.meta, + }) + trackEvent('tool_selected', { + tool_name: payload.name, + plugin_id: provider.plugin_id, + }) + }} + > +
+ {payload.label[language]} +
+ {isAdded && ( +
{t('addToolModal.added', { ns: 'tools' })}
+ )} + + ) + + return ( + // Preview is supplementary: provider icon, tool label and description are all + // reachable from the node inspector after the row is clicked to add the tool, + // so hover/focus-only activation is a11y-safe. See + // packages/dify-ui/AGENTS.md → Overlay Primitive Selection. + + +
= ({
{payload.label[language]}
{payload.description[language]}
- )} - > -
{ - if (disabled) - return - const params: Record = {} - if (payload.parameters) { - payload.parameters.forEach((item) => { - params[item.name] = '' - }) - } - onSelect(BlockEnum.Tool, { - provider_id: provider.id, - provider_type: provider.type, - provider_name: provider.name, - plugin_id: provider.plugin_id, - plugin_unique_identifier: provider.plugin_unique_identifier, - provider_icon: normalizedIcon, - provider_icon_dark: normalizedIconDark, - tool_name: payload.name, - tool_label: payload.label[language]!, - tool_description: payload.description[language], - title: payload.label[language]!, - is_team_authorization: provider.is_team_authorization, - paramSchemas: payload.parameters, - params, - meta: provider.meta, - }) - trackEvent('tool_selected', { - tool_name: payload.name, - plugin_id: provider.plugin_id, - }) - }} - > -
- {payload.label[language]} -
- {isAdded && ( -
{t('addToolModal.added', { ns: 'tools' })}
- )} -
-
+ + ) } export default React.memo(ToolItem) diff --git a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx index 4ffb84e1b8..38c4c2b0f5 100644 --- a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx +++ b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx @@ -3,9 +3,9 @@ import type { FC } from 'react' import type { TriggerDefaultValue, TriggerWithProvider } from '../types' import type { Event } from '@/app/components/tools/types' import { cn } from '@langgenius/dify-ui/cn' +import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' import { useGetLanguage } from '@/context/i18n' import BlockIcon from '../../block-icon' import { BlockEnum } from '../../types' @@ -28,13 +28,54 @@ const TriggerPluginActionItem: FC = ({ const { t } = useTranslation() const language = useGetLanguage() - return ( - { + if (disabled) + return + const params: Record = {} + if (payload.parameters) { + payload.parameters.forEach((item: any) => { + params[item.name] = '' + }) + } + onSelect(BlockEnum.TriggerPlugin, { + plugin_id: provider.plugin_id, + provider_id: provider.name, + provider_type: provider.type as string, + provider_name: provider.name, + event_name: payload.name, + event_label: payload.label[language]!, + event_description: payload.description[language]!, + plugin_unique_identifier: provider.plugin_unique_identifier, + title: payload.label[language]!, + is_team_authorization: provider.is_team_authorization, + output_schema: payload.output_schema || {}, + paramSchemas: payload.parameters, + params, + meta: provider.meta, + }) + }} + > +
+ {payload.label[language]} +
+ {isAdded && ( +
{t('addToolModal.added', { ns: 'tools' })}
+ )} + + ) + + return ( + // Preview is supplementary: provider icon, event label and description are all + // reachable from the node inspector after the row is clicked to add the trigger, + // so hover/focus-only activation is a11y-safe. See + // packages/dify-ui/AGENTS.md → Overlay Primitive Selection. + + +
= ({
{payload.label[language]}
{payload.description[language]}
- )} - > -
{ - if (disabled) - return - const params: Record = {} - if (payload.parameters) { - payload.parameters.forEach((item: any) => { - params[item.name] = '' - }) - } - onSelect(BlockEnum.TriggerPlugin, { - plugin_id: provider.plugin_id, - provider_id: provider.name, - provider_type: provider.type as string, - provider_name: provider.name, - event_name: payload.name, - event_label: payload.label[language]!, - event_description: payload.description[language]!, - plugin_unique_identifier: provider.plugin_unique_identifier, - title: payload.label[language]!, - is_team_authorization: provider.is_team_authorization, - output_schema: payload.output_schema || {}, - paramSchemas: payload.parameters, - params, - meta: provider.meta, - }) - }} - > -
- {payload.label[language]} -
- {isAdded && ( -
{t('addToolModal.added', { ns: 'tools' })}
- )} -
-
+ + ) } export default React.memo(TriggerPluginActionItem) diff --git a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx index f7264bcc99..c401377450 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx @@ -28,7 +28,7 @@ const createProps = ( readonly: false, setControlFocus: vi.fn(), setOpen: vi.fn(), - tooltipPopup: null, + hoverPopup: null, triggerRef: { current: null }, value: [], varKindType: VarKindType.constant, diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx index 8c46118615..5a0362a07e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx @@ -1,12 +1,13 @@ 'use client' -import type { FC, ReactNode } from 'react' +import type { FC, ReactElement } from 'react' import type { VarType as VarKindType } from '../../../tool/types' import type { CredentialFormSchema, CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Tool } from '@/app/components/tools/types' import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' import type { Node, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' +import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiArrowDownSLine, RiCloseLine, RiErrorWarningFill, RiLoader4Line, RiMoreLine } from '@remixicon/react' import Badge from '@/app/components/base/badge' @@ -18,6 +19,10 @@ import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/com import RemoveButton from '../remove-button' import ConstantField from './constant-field' +export type HoverPopup + = | { kind: 'full-path', panel: ReactElement } + | { kind: 'invalid-variable', message: string } + type Props = { className?: string controlFocus: number @@ -53,7 +58,7 @@ type Props = { setControlFocus: (value: number) => void setOpen: (value: boolean) => void showErrorIcon?: boolean - tooltipPopup: ReactNode + hoverPopup: HoverPopup | null triggerRef: React.RefObject type?: string typePlaceHolder?: string @@ -99,7 +104,7 @@ const VarReferencePickerTrigger: FC = ({ setControlFocus, setOpen, showErrorIcon = false, - tooltipPopup, + hoverPopup, triggerRef, type, typePlaceHolder, @@ -112,6 +117,101 @@ const VarReferencePickerTrigger: FC = ({ VarPickerWrap, WrapElem, }) => { + const pill = ( +
+ {hasValue + ? ( + <> + {isShowNodeName && ( +
{ + if (e.metaKey || e.ctrlKey) + handleVariableJump(outputVarNodeId || '') + }} + > +
+ {'type' in (outputVarNode || {}) && outputVarNode?.type && ( + + )} +
+
+ {outputVarNode?.title as string | undefined} +
+ +
+ )} + {isShowAPart && ( +
+ + +
+ )} +
+ {isLoading && } + +
+ {varName} +
+
+
+ {type} +
+ {showErrorIcon && } + + ) + : ( +
+ {isLoading + ? ( +
+ + {placeholder} +
+ ) + : placeholder} +
+ )} +
+ ) + + const hoveredPill = hoverPopup?.kind === 'full-path' + ? ( + + + + {hoverPopup.panel} + + + ) + : hoverPopup?.kind === 'invalid-variable' + ? ( + + + {hoverPopup.message} + + ) + : pill + return ( { @@ -191,92 +291,7 @@ const VarReferencePickerTrigger: FC = ({ className="h-full grow" >
- - - {hasValue - ? ( - <> - {isShowNodeName && ( -
{ - if (e.metaKey || e.ctrlKey) - handleVariableJump(outputVarNodeId || '') - }} - > -
- {'type' in (outputVarNode || {}) && outputVarNode?.type && ( - - )} -
-
- {outputVarNode?.title as string | undefined} -
- -
- )} - {isShowAPart && ( -
- - -
- )} -
- {isLoading && } - -
- {varName} -
-
-
- {type} -
- {showErrorIcon && } - - ) - : ( -
- {isLoading - ? ( -
- - {placeholder} -
- ) - : placeholder} -
- )} -
- )} - /> - {tooltipPopup !== null && tooltipPopup !== undefined && ( - - {tooltipPopup} - - )} - + {hoveredPill} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 1cfccf1622..9b985c8a9e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -1,5 +1,6 @@ 'use client' import type { FC } from 'react' +import type { HoverPopup } from './var-reference-picker.trigger' import type { CredentialFormSchema, CredentialFormSchemaSelect, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Tool } from '@/app/components/tools/types' import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' @@ -291,20 +292,23 @@ const VarReferencePicker: FC = ({ const WrapElem = isSupportConstantValue ? 'div' : PortalToFollowElemTrigger const VarPickerWrap = !isSupportConstantValue ? 'div' : PortalToFollowElemTrigger - const tooltipPopup = useMemo(() => { + const hoverPopup = useMemo(() => { const tooltipType = getTooltipContent(hasValue, isShowAPart, isValidVar) if (tooltipType === 'full-path') { - return ( - - ) + return { + kind: 'full-path', + panel: ( + + ), + } } if (tooltipType === 'invalid-variable') - return t('errorMsg.invalidVariable', { ns: 'workflow' }) + return { kind: 'invalid-variable', message: t('errorMsg.invalidVariable', { ns: 'workflow' }) } return null }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value, type]) @@ -389,7 +393,7 @@ const VarReferencePicker: FC = ({ setControlFocus={setControlFocus} setOpen={setOpen} showErrorIcon={showErrorIcon} - tooltipPopup={tooltipPopup} + hoverPopup={hoverPopup} triggerRef={triggerRef} type={type} typePlaceHolder={typePlaceHolder} From 77d6c108e715d0bd50e8a48c257f221442894caa Mon Sep 17 00:00:00 2001 From: -LAN- Date: Tue, 21 Apr 2026 12:29:35 +0800 Subject: [PATCH 018/253] fix(workflow): cache provider configurations during graph init (#35447) --- api/core/provider_manager.py | 26 +++++++ .../unit_tests/core/test_provider_manager.py | 72 +++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/api/core/provider_manager.py b/api/core/provider_manager.py index c3bbe8fc09..8969825be4 100644 --- a/api/core/provider_manager.py +++ b/api/core/provider_manager.py @@ -70,12 +70,32 @@ class ProviderManager: Request-bound managers may carry caller identity in that runtime, and the resulting ``ProviderConfiguration`` objects must reuse it for downstream model-type and schema lookups. + + Configuration assembly is cached per manager instance so call chains that + share one request-scoped manager can reuse the same provider graph instead + of rebuilding it for every lookup. Call ``clear_configurations_cache()`` + when a long-lived manager needs to observe writes performed within the same + instance scope. """ + decoding_rsa_key: Any | None + decoding_cipher_rsa: Any | None + _model_runtime: ModelRuntime + _configurations_cache: dict[str, ProviderConfigurations] + def __init__(self, model_runtime: ModelRuntime): self.decoding_rsa_key = None self.decoding_cipher_rsa = None self._model_runtime = model_runtime + self._configurations_cache = {} + + def clear_configurations_cache(self, tenant_id: str | None = None) -> None: + """Drop assembled provider configurations cached on this manager instance.""" + if tenant_id is None: + self._configurations_cache.clear() + return + + self._configurations_cache.pop(tenant_id, None) def get_configurations(self, tenant_id: str) -> ProviderConfigurations: """ @@ -114,6 +134,10 @@ class ProviderManager: :param tenant_id: :return: """ + cached_configurations = self._configurations_cache.get(tenant_id) + if cached_configurations is not None: + return cached_configurations + # Get all provider records of the workspace provider_name_to_provider_records_dict = self._get_all_providers(tenant_id) @@ -273,6 +297,8 @@ class ProviderManager: provider_configurations[str(provider_id_entity)] = provider_configuration + self._configurations_cache[tenant_id] = provider_configurations + # Return the encapsulated object return provider_configurations diff --git a/api/tests/unit_tests/core/test_provider_manager.py b/api/tests/unit_tests/core/test_provider_manager.py index f45b43082c..a5a542c94f 100644 --- a/api/tests/unit_tests/core/test_provider_manager.py +++ b/api/tests/unit_tests/core/test_provider_manager.py @@ -372,6 +372,78 @@ def test_get_configurations_binds_manager_runtime_to_provider_configuration( provider_configuration.bind_model_runtime.assert_called_once_with(manager._model_runtime) +def test_get_configurations_reuses_cached_result_for_same_tenant(mocker: MockerFixture, mock_provider_entity): + manager = _build_provider_manager(mocker) + provider_configuration = Mock() + provider_factory = Mock() + provider_factory.get_providers.return_value = [mock_provider_entity] + custom_configuration = SimpleNamespace(provider=None, models=[]) + system_configuration = SimpleNamespace(enabled=False, quota_configurations=[], current_quota_type=None) + + with ( + patch.object(manager, "_get_all_providers", return_value={"openai": []}) as mock_get_all_providers, + patch.object(manager, "_init_trial_provider_records", return_value={"openai": []}), + patch.object(manager, "_get_all_provider_models", return_value={"openai": []}), + patch.object(manager, "_get_all_preferred_model_providers", return_value={}), + patch.object(manager, "_get_all_provider_model_settings", return_value={}), + patch.object(manager, "_get_all_provider_load_balancing_configs", return_value={}), + patch.object(manager, "_get_all_provider_model_credentials", return_value={}), + patch.object(manager, "_to_custom_configuration", return_value=custom_configuration), + patch.object(manager, "_to_system_configuration", return_value=system_configuration), + patch.object(manager, "_to_model_settings", return_value=[]), + patch("core.provider_manager.ModelProviderFactory", return_value=provider_factory) as mock_factory_cls, + patch( + "core.provider_manager.ProviderConfiguration", + return_value=provider_configuration, + ) as mock_provider_configuration, + ): + first = manager.get_configurations("tenant-id") + second = manager.get_configurations("tenant-id") + + assert first is second + mock_get_all_providers.assert_called_once_with("tenant-id") + mock_factory_cls.assert_called_once_with(model_runtime=manager._model_runtime) + mock_provider_configuration.assert_called_once() + provider_configuration.bind_model_runtime.assert_called_once_with(manager._model_runtime) + + +def test_clear_configurations_cache_rebuilds_requested_tenant(mocker: MockerFixture, mock_provider_entity): + manager = _build_provider_manager(mocker) + provider_factory = Mock() + provider_factory.get_providers.return_value = [mock_provider_entity] + custom_configuration = SimpleNamespace(provider=None, models=[]) + system_configuration = SimpleNamespace(enabled=False, quota_configurations=[], current_quota_type=None) + provider_configuration_first = Mock() + provider_configuration_second = Mock() + + with ( + patch.object(manager, "_get_all_providers", return_value={"openai": []}) as mock_get_all_providers, + patch.object(manager, "_init_trial_provider_records", return_value={"openai": []}), + patch.object(manager, "_get_all_provider_models", return_value={"openai": []}), + patch.object(manager, "_get_all_preferred_model_providers", return_value={}), + patch.object(manager, "_get_all_provider_model_settings", return_value={}), + patch.object(manager, "_get_all_provider_load_balancing_configs", return_value={}), + patch.object(manager, "_get_all_provider_model_credentials", return_value={}), + patch.object(manager, "_to_custom_configuration", return_value=custom_configuration), + patch.object(manager, "_to_system_configuration", return_value=system_configuration), + patch.object(manager, "_to_model_settings", return_value=[]), + patch("core.provider_manager.ModelProviderFactory", return_value=provider_factory), + patch( + "core.provider_manager.ProviderConfiguration", + side_effect=[provider_configuration_first, provider_configuration_second], + ) as mock_provider_configuration, + ): + first = manager.get_configurations("tenant-id") + manager.clear_configurations_cache("tenant-id") + second = manager.get_configurations("tenant-id") + + assert first is not second + assert mock_get_all_providers.call_count == 2 + assert mock_provider_configuration.call_count == 2 + provider_configuration_first.bind_model_runtime.assert_called_once_with(manager._model_runtime) + provider_configuration_second.bind_model_runtime.assert_called_once_with(manager._model_runtime) + + def test_get_provider_model_bundle_returns_selected_model_type_instance(mocker: MockerFixture): manager = _build_provider_manager(mocker) provider_configuration = Mock() From 77f8f2babb274bdfec4ccb4dd942f2cf53e0e49a Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Tue, 21 Apr 2026 06:32:43 +0200 Subject: [PATCH 019/253] refactor(api): tighten core rag typing batch 1 (#35210) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../rag/datasource/keyword/jieba/jieba.py | 4 +- .../jieba/jieba_keyword_table_handler.py | 9 +++-- api/core/rag/datasource/retrieval_service.py | 2 +- api/core/rag/extractor/extract_processor.py | 2 + .../multi_dataset_function_call_router.py | 2 +- api/core/rag/splitter/fixed_text_splitter.py | 7 ++-- api/core/rag/splitter/text_splitter.py | 11 +++--- api/services/hit_testing_service.py | 39 +++++++++++-------- 8 files changed, 46 insertions(+), 30 deletions(-) diff --git a/api/core/rag/datasource/keyword/jieba/jieba.py b/api/core/rag/datasource/keyword/jieba/jieba.py index ed264878d3..242da520c1 100644 --- a/api/core/rag/datasource/keyword/jieba/jieba.py +++ b/api/core/rag/datasource/keyword/jieba/jieba.py @@ -139,8 +139,10 @@ class Jieba(BaseKeyword): "__data__": {"index_id": self.dataset.id, "summary": None, "table": keyword_table}, } dataset_keyword_table = self.dataset.dataset_keyword_table - keyword_data_source_type = dataset_keyword_table.data_source_type + keyword_data_source_type = dataset_keyword_table.data_source_type if dataset_keyword_table else "file" if keyword_data_source_type == "database": + if dataset_keyword_table is None: + return dataset_keyword_table.keyword_table = dumps_with_sets(keyword_table_dict) db.session.commit() else: diff --git a/api/core/rag/datasource/keyword/jieba/jieba_keyword_table_handler.py b/api/core/rag/datasource/keyword/jieba/jieba_keyword_table_handler.py index 84f35c25f8..1ca6303af6 100644 --- a/api/core/rag/datasource/keyword/jieba/jieba_keyword_table_handler.py +++ b/api/core/rag/datasource/keyword/jieba/jieba_keyword_table_handler.py @@ -1,4 +1,5 @@ import re +from collections.abc import Callable from operator import itemgetter from typing import cast @@ -80,12 +81,14 @@ class JiebaKeywordTableHandler: def extract_tags(self, sentence: str, top_k: int | None = 20, **kwargs): # Basic frequency-based keyword extraction as a fallback when TF-IDF is unavailable. - top_k = kwargs.pop("topK", top_k) + top_k = cast(int | None, kwargs.pop("topK", top_k)) + if top_k is None: + top_k = 20 cut = getattr(jieba, "cut", None) if self._lcut: tokens = self._lcut(sentence) elif callable(cut): - tokens = list(cut(sentence)) + tokens = list(cast(Callable[[str], list[str]], cut)(sentence)) else: tokens = re.findall(r"\w+", sentence) @@ -108,7 +111,7 @@ class JiebaKeywordTableHandler: sentence=text, topK=max_keywords_per_chunk, ) - # jieba.analyse.extract_tags returns list[Any] when withFlag is False by default. + # jieba.analyse.extract_tags returns an untyped list when withFlag is False by default. keywords = cast(list[str], keywords) return set(self._expand_tokens_with_subtokens(set(keywords))) diff --git a/api/core/rag/datasource/retrieval_service.py b/api/core/rag/datasource/retrieval_service.py index 7e71d67ec0..2997710daf 100644 --- a/api/core/rag/datasource/retrieval_service.py +++ b/api/core/rag/datasource/retrieval_service.py @@ -158,7 +158,7 @@ class RetrievalService: ) if futures: - for future in concurrent.futures.as_completed(futures, timeout=3600): + for _ in concurrent.futures.as_completed(futures, timeout=3600): if exceptions: for f in futures: f.cancel() diff --git a/api/core/rag/extractor/extract_processor.py b/api/core/rag/extractor/extract_processor.py index fbd2a6db93..b679edab36 100644 --- a/api/core/rag/extractor/extract_processor.py +++ b/api/core/rag/extractor/extract_processor.py @@ -94,6 +94,7 @@ class ExtractProcessor: cls, extract_setting: ExtractSetting, is_automatic: bool = False, file_path: str | None = None ) -> list[Document]: if extract_setting.datasource_type == DatasourceType.FILE: + upload_file = extract_setting.upload_file with tempfile.TemporaryDirectory() as temp_dir: upload_file = extract_setting.upload_file if not file_path: @@ -104,6 +105,7 @@ class ExtractProcessor: storage.download(upload_file.key, file_path) input_file = Path(file_path) file_extension = input_file.suffix.lower() + assert upload_file is not None, "upload_file is required" etl_type = dify_config.ETL_TYPE extractor: BaseExtractor | None = None if etl_type == "Unstructured": diff --git a/api/core/rag/retrieval/router/multi_dataset_function_call_router.py b/api/core/rag/retrieval/router/multi_dataset_function_call_router.py index e617a9660e..426d1b67dc 100644 --- a/api/core/rag/retrieval/router/multi_dataset_function_call_router.py +++ b/api/core/rag/retrieval/router/multi_dataset_function_call_router.py @@ -28,7 +28,7 @@ class FunctionCallMultiDatasetRouter: SystemPromptMessage(content="You are a helpful AI assistant."), UserPromptMessage(content=query), ] - result: LLMResult = model_instance.invoke_llm( + result: LLMResult = model_instance.invoke_llm( # pyright: ignore[reportCallIssue, reportArgumentType] prompt_messages=prompt_messages, tools=dataset_tools, stream=False, diff --git a/api/core/rag/splitter/fixed_text_splitter.py b/api/core/rag/splitter/fixed_text_splitter.py index 66b375dad1..52c9a02f97 100644 --- a/api/core/rag/splitter/fixed_text_splitter.py +++ b/api/core/rag/splitter/fixed_text_splitter.py @@ -4,7 +4,7 @@ from __future__ import annotations import codecs import re -from collections.abc import Collection +from collections.abc import Set as AbstractSet from typing import Any, Literal from core.model_manager import ModelInstance @@ -21,8 +21,8 @@ class EnhanceRecursiveCharacterTextSplitter(RecursiveCharacterTextSplitter): def from_encoder[T: EnhanceRecursiveCharacterTextSplitter]( cls: type[T], embedding_model_instance: ModelInstance | None, - allowed_special: Literal["all"] | set[str] = set(), - disallowed_special: Literal["all"] | Collection[str] = "all", + allowed_special: Literal["all"] | AbstractSet[str] = frozenset(), + disallowed_special: Literal["all"] | AbstractSet[str] = "all", **kwargs: Any, ) -> T: def _token_encoder(texts: list[str]) -> list[int]: @@ -40,6 +40,7 @@ class EnhanceRecursiveCharacterTextSplitter(RecursiveCharacterTextSplitter): return [len(text) for text in texts] + _ = _token_encoder # kept for future token-length wiring return cls(length_function=_character_encoder, **kwargs) diff --git a/api/core/rag/splitter/text_splitter.py b/api/core/rag/splitter/text_splitter.py index 7f2117e2dd..a8d9013fbc 100644 --- a/api/core/rag/splitter/text_splitter.py +++ b/api/core/rag/splitter/text_splitter.py @@ -4,7 +4,8 @@ import copy import logging import re from abc import ABC, abstractmethod -from collections.abc import Callable, Collection, Iterable, Sequence, Set +from collections.abc import Callable, Iterable, Sequence +from collections.abc import Set as AbstractSet from dataclasses import dataclass from typing import Any, Literal @@ -187,8 +188,8 @@ class TokenTextSplitter(TextSplitter): self, encoding_name: str = "gpt2", model_name: str | None = None, - allowed_special: Literal["all"] | Set[str] = set(), - disallowed_special: Literal["all"] | Collection[str] = "all", + allowed_special: Literal["all"] | AbstractSet[str] = frozenset(), + disallowed_special: Literal["all"] | AbstractSet[str] = "all", **kwargs: Any, ): """Create a new TextSplitter.""" @@ -207,8 +208,8 @@ class TokenTextSplitter(TextSplitter): else: enc = tiktoken.get_encoding(encoding_name) self._tokenizer = enc - self._allowed_special = allowed_special - self._disallowed_special = disallowed_special + self._allowed_special: Literal["all"] | AbstractSet[str] = allowed_special + self._disallowed_special: Literal["all"] | AbstractSet[str] = disallowed_special def split_text(self, text: str) -> list[str]: def _encode(_text: str) -> list[int]: diff --git a/api/services/hit_testing_service.py b/api/services/hit_testing_service.py index ca84b2a3d8..2e5987dd28 100644 --- a/api/services/hit_testing_service.py +++ b/api/services/hit_testing_service.py @@ -1,10 +1,10 @@ import json import logging import time -from typing import Any, TypedDict +from typing import Any, TypedDict, cast from core.app.app_config.entities import ModelConfig -from core.rag.datasource.retrieval_service import RetrievalService +from core.rag.datasource.retrieval_service import DefaultRetrievalModelDict, RetrievalService from core.rag.index_processor.constant.query_type import QueryType from core.rag.models.document import Document from core.rag.retrieval.dataset_retrieval import DatasetRetrieval @@ -36,6 +36,10 @@ default_retrieval_model = { } +class HitTestingRetrievalModelDict(DefaultRetrievalModelDict, total=False): + metadata_filtering_conditions: dict[str, Any] + + class HitTestingService: @classmethod def retrieve( @@ -51,17 +55,18 @@ class HitTestingService: start = time.perf_counter() # get retrieval model , if the model is not setting , using default - if not retrieval_model: - retrieval_model = dataset.retrieval_model or default_retrieval_model - assert isinstance(retrieval_model, dict) + resolved_retrieval_model = cast( + HitTestingRetrievalModelDict, + retrieval_model or dataset.retrieval_model or default_retrieval_model, + ) document_ids_filter = None - metadata_filtering_conditions = retrieval_model.get("metadata_filtering_conditions", {}) - if metadata_filtering_conditions and query: + metadata_filtering_conditions_raw = resolved_retrieval_model.get("metadata_filtering_conditions", {}) + if metadata_filtering_conditions_raw and query: dataset_retrieval = DatasetRetrieval() from core.rag.entities import MetadataFilteringCondition - metadata_filtering_conditions = MetadataFilteringCondition.model_validate(metadata_filtering_conditions) + metadata_filtering_conditions = MetadataFilteringCondition.model_validate(metadata_filtering_conditions_raw) metadata_filter_document_ids, metadata_condition = dataset_retrieval.get_metadata_filter_condition( dataset_ids=[dataset.id], @@ -78,19 +83,21 @@ class HitTestingService: if metadata_condition and not document_ids_filter: return cls.compact_retrieve_response(query, []) all_documents = RetrievalService.retrieve( - retrieval_method=RetrievalMethod(retrieval_model.get("search_method", RetrievalMethod.SEMANTIC_SEARCH)), + retrieval_method=RetrievalMethod( + resolved_retrieval_model.get("search_method", RetrievalMethod.SEMANTIC_SEARCH) + ), dataset_id=dataset.id, query=query, attachment_ids=attachment_ids, - top_k=retrieval_model.get("top_k", 4), - score_threshold=retrieval_model.get("score_threshold", 0.0) - if retrieval_model["score_threshold_enabled"] + top_k=resolved_retrieval_model.get("top_k", 4), + score_threshold=resolved_retrieval_model.get("score_threshold", 0.0) + if resolved_retrieval_model["score_threshold_enabled"] else 0.0, - reranking_model=retrieval_model.get("reranking_model", None) - if retrieval_model["reranking_enable"] + reranking_model=resolved_retrieval_model.get("reranking_model", None) + if resolved_retrieval_model["reranking_enable"] else None, - reranking_mode=retrieval_model.get("reranking_mode") or "reranking_model", - weights=retrieval_model.get("weights", None), + reranking_mode=resolved_retrieval_model.get("reranking_mode") or "reranking_model", + weights=resolved_retrieval_model.get("weights", None), document_ids_filter=document_ids_filter, ) From dc83e8aa0997227e7ba1e26b6d9aa07ad70ae288 Mon Sep 17 00:00:00 2001 From: agenthaulk Date: Tue, 21 Apr 2026 12:43:45 +0800 Subject: [PATCH 020/253] chore: resolve oxlint warnings across web and SDK (#34540) Co-authored-by: Asuka Minato --- sdks/nodejs-client/src/http/client.test.ts | 4 ++-- sdks/nodejs-client/src/index.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdks/nodejs-client/src/http/client.test.ts b/sdks/nodejs-client/src/http/client.test.ts index af859801c6..4dc2087782 100644 --- a/sdks/nodejs-client/src/http/client.test.ts +++ b/sdks/nodejs-client/src/http/client.test.ts @@ -39,7 +39,7 @@ const jsonResponse = ( ...init, headers: { "content-type": "application/json", - ...(init.headers ?? {}), + ...init.headers, }, }); @@ -47,7 +47,7 @@ const textResponse = (body: string, init: ResponseInit = {}): Response => new Response(body, { ...init, headers: { - ...(init.headers ?? {}), + ...init.headers, }, }); diff --git a/sdks/nodejs-client/src/index.test.ts b/sdks/nodejs-client/src/index.test.ts index 8d56b994c4..b323371891 100644 --- a/sdks/nodejs-client/src/index.test.ts +++ b/sdks/nodejs-client/src/index.test.ts @@ -14,7 +14,7 @@ const jsonResponse = (body: unknown, init: ResponseInit = {}): Response => ...init, headers: { "content-type": "application/json", - ...(init.headers ?? {}), + ...init.headers, }, }); From 051ba99cd2f6b6ef2cc22b707a356878fb754049 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:00:02 +0800 Subject: [PATCH 021/253] refactor(billing): use Infotip for UsageInfo help icon, migrate storage tooltip to dify-ui (#35448) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 5 ---- .../usage-info/__tests__/index.spec.tsx | 2 +- .../__tests__/vector-space-info.spec.tsx | 3 ++- .../components/billing/usage-info/index.tsx | 23 ++++++++----------- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index b4840a2eff..d969e0bf83 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2218,11 +2218,6 @@ "count": 3 } }, - "web/app/components/billing/usage-info/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/common/image-previewer/index.tsx": { "no-irregular-whitespace": { "count": 1 diff --git a/web/app/components/billing/usage-info/__tests__/index.spec.tsx b/web/app/components/billing/usage-info/__tests__/index.spec.tsx index 073c2e7fe2..3322d35809 100644 --- a/web/app/components/billing/usage-info/__tests__/index.spec.tsx +++ b/web/app/components/billing/usage-info/__tests__/index.spec.tsx @@ -309,7 +309,7 @@ describe('UsageInfo', () => { />, ) - expect(container.querySelector('[data-state]')).toBeInTheDocument() + expect(container.querySelectorAll('.cursor-default').length).toBeGreaterThan(0) }) }) }) diff --git a/web/app/components/billing/usage-info/__tests__/vector-space-info.spec.tsx b/web/app/components/billing/usage-info/__tests__/vector-space-info.spec.tsx index 43e132c00f..3422d09c2f 100644 --- a/web/app/components/billing/usage-info/__tests__/vector-space-info.spec.tsx +++ b/web/app/components/billing/usage-info/__tests__/vector-space-info.spec.tsx @@ -3,7 +3,8 @@ import { defaultPlan } from '../../config' import { Plan } from '../../type' import VectorSpaceInfo from '../vector-space-info' -const queryPlaceholder = () => document.body.querySelector('[aria-hidden="true"]') +const queryPlaceholder = () => + document.body.querySelector('[aria-hidden="true"].bg-components-progress-bar-bg') // Mock provider context with configurable plan let mockPlanType = Plan.sandbox diff --git a/web/app/components/billing/usage-info/index.tsx b/web/app/components/billing/usage-info/index.tsx index 68e889f320..ee1929863f 100644 --- a/web/app/components/billing/usage-info/index.tsx +++ b/web/app/components/billing/usage-info/index.tsx @@ -3,9 +3,10 @@ import type { MeterTone } from '@langgenius/dify-ui/meter' import type { ComponentType, FC, ReactNode } from 'react' import { cn } from '@langgenius/dify-ui/cn' import { MeterIndicator, MeterRoot, MeterTrack } from '@langgenius/dify-ui/meter' +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import { NUM_INFINITE } from '../config' type Props = { @@ -159,11 +160,11 @@ const UsageInfo: FC = ({ const wrapWithStorageTooltip = (children: ReactNode) => { if (storageMode && storageTooltip) { return ( - {storageTooltip}} - asChild={false} - > -
{children}
+ + {children}} /> + + {storageTooltip} + ) } @@ -178,13 +179,9 @@ const UsageInfo: FC = ({
{name}
{tooltip && ( - - {tooltip} -
- )} - /> + + {tooltip} + )}
From 3b24d8d2d14ff6ff98bf0e7cddc7406a71e20bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Tue, 21 Apr 2026 14:04:56 +0800 Subject: [PATCH 022/253] fix: webscaper sometime not work (#35450) --- api/core/tools/utils/web_reader_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/tools/utils/web_reader_tool.py b/api/core/tools/utils/web_reader_tool.py index ed3ed3e0de..94a2c0427b 100644 --- a/api/core/tools/utils/web_reader_tool.py +++ b/api/core/tools/utils/web_reader_tool.py @@ -105,7 +105,7 @@ class Article: def extract_using_readabilipy(html: str): - json_article: dict[str, Any] = simple_json_from_html_string(html, use_readability=True) + json_article: dict[str, Any] = simple_json_from_html_string(html, use_readability=False) article = Article( title=json_article.get("title") or "", author=json_article.get("byline") or "", From 0b60bf6ef081f7bc092b33052a33e13db1f6658f Mon Sep 17 00:00:00 2001 From: Mallikharjuna Mulpuri Date: Tue, 21 Apr 2026 03:44:49 -0400 Subject: [PATCH 023/253] refactor: replace deprecated Iterator with Generator in contextmanagers #35433 (#35441) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/core/app/file_access/scope.py | 4 ++-- api/libs/flask_utils.py | 4 ++-- .../src/dify_vdb_analyticdb/analyticdb_vector_sql.py | 4 ++-- api/services/file_service.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/core/app/file_access/scope.py b/api/core/app/file_access/scope.py index 80d504ef1c..a583301f9b 100644 --- a/api/core/app/file_access/scope.py +++ b/api/core/app/file_access/scope.py @@ -1,6 +1,6 @@ from __future__ import annotations -from collections.abc import Iterator +from collections.abc import Generator # Changed from Iterator from contextlib import contextmanager from contextvars import ContextVar from dataclasses import dataclass @@ -32,7 +32,7 @@ def get_current_file_access_scope() -> FileAccessScope | None: @contextmanager -def bind_file_access_scope(scope: FileAccessScope) -> Iterator[None]: +def bind_file_access_scope(scope: FileAccessScope) -> Generator[None, None, None]: # Changed from Iterator[None] token = _current_file_access_scope.set(scope) try: yield diff --git a/api/libs/flask_utils.py b/api/libs/flask_utils.py index 52fc787c79..838af2bf32 100644 --- a/api/libs/flask_utils.py +++ b/api/libs/flask_utils.py @@ -1,5 +1,5 @@ import contextvars -from collections.abc import Iterator +from collections.abc import Generator # Changed from Iterator from contextlib import contextmanager from typing import TYPE_CHECKING @@ -13,7 +13,7 @@ if TYPE_CHECKING: def preserve_flask_contexts( flask_app: Flask, context_vars: contextvars.Context, -) -> Iterator[None]: +) -> Generator[None, None, None]: # Changed from Iterator[None] """ A context manager that handles: 1. flask-login's UserProxy copy diff --git a/api/providers/vdb/vdb-analyticdb/src/dify_vdb_analyticdb/analyticdb_vector_sql.py b/api/providers/vdb/vdb-analyticdb/src/dify_vdb_analyticdb/analyticdb_vector_sql.py index b2908ebdae..11398efb58 100644 --- a/api/providers/vdb/vdb-analyticdb/src/dify_vdb_analyticdb/analyticdb_vector_sql.py +++ b/api/providers/vdb/vdb-analyticdb/src/dify_vdb_analyticdb/analyticdb_vector_sql.py @@ -1,6 +1,6 @@ import json import uuid -from collections.abc import Iterator +from collections.abc import Generator # Added Generator from contextlib import contextmanager from typing import Any @@ -75,7 +75,7 @@ class AnalyticdbVectorBySql: ) @contextmanager - def _get_cursor(self) -> Iterator[Any]: + def _get_cursor(self) -> Generator[Any, None, None]: # Changed from Iterator[Any] assert self.pool is not None, "Connection pool is not initialized" conn = self.pool.getconn() cur = conn.cursor() diff --git a/api/services/file_service.py b/api/services/file_service.py index 52da2a7951..f60afe2f19 100644 --- a/api/services/file_service.py +++ b/api/services/file_service.py @@ -2,7 +2,7 @@ import base64 import hashlib import os import uuid -from collections.abc import Iterator, Sequence +from collections.abc import Generator, Sequence # Changed Iterator to Generator from contextlib import contextmanager, suppress from tempfile import NamedTemporaryFile from typing import Literal @@ -324,7 +324,7 @@ class FileService: def build_upload_files_zip_tempfile( *, upload_files: Sequence[UploadFile], - ) -> Iterator[str]: + ) -> Generator[str, None, None]: # Changed from Iterator[str] """ Build a ZIP from `UploadFile`s and yield a tempfile path. From 48d23cd74429fbd3672f09b7830f981e0d0d42cd Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 21 Apr 2026 15:57:16 +0800 Subject: [PATCH 024/253] feat: support slash variable filtering in prompt editor (#35460) --- .../prompt-editor/__tests__/hooks.spec.tsx | 10 +-- .../components/base/prompt-editor/hooks.ts | 7 +- .../__tests__/index.spec.tsx | 78 +++++++++++++++++++ .../plugins/component-picker-block/index.tsx | 21 +++-- .../var-reference-vars.helpers.spec.ts | 23 ++++++ .../__tests__/var-reference-vars.spec.tsx | 28 +++++++ .../variable/var-reference-vars.helpers.ts | 50 +++++++++++- .../variable/var-reference-vars.tsx | 13 ++-- 8 files changed, 210 insertions(+), 20 deletions(-) diff --git a/web/app/components/base/prompt-editor/__tests__/hooks.spec.tsx b/web/app/components/base/prompt-editor/__tests__/hooks.spec.tsx index 9917918628..13c0c05803 100644 --- a/web/app/components/base/prompt-editor/__tests__/hooks.spec.tsx +++ b/web/app/components/base/prompt-editor/__tests__/hooks.spec.tsx @@ -423,11 +423,11 @@ describe('prompt-editor/hooks', () => { maxLength: 5, })) - const match = result.current('prefix @..', {} as LexicalEditor) + const match = result.current('prefix @ab', {} as LexicalEditor) expect(match).toEqual({ leadOffset: 7, - matchingString: '..', - replaceableString: '@..', + matchingString: 'ab', + replaceableString: '@ab', }) }) @@ -437,7 +437,7 @@ describe('prompt-editor/hooks', () => { maxLength: 5, })) - expect(result.current('prefix @.', {} as LexicalEditor)).toBeNull() + expect(result.current('prefix @a', {} as LexicalEditor)).toBeNull() }) it('should return null when matching text exceeds maxLength', () => { @@ -445,7 +445,7 @@ describe('prompt-editor/hooks', () => { minLength: 1, maxLength: 2, })) - expect(result.current('prefix @...', {} as LexicalEditor)).toBeNull() + expect(result.current('prefix @abc', {} as LexicalEditor)).toBeNull() }) it('should return null when text has no trigger character', () => { diff --git a/web/app/components/base/prompt-editor/hooks.ts b/web/app/components/base/prompt-editor/hooks.ts index dd6c6295a6..70869d6a17 100644 --- a/web/app/components/base/prompt-editor/hooks.ts +++ b/web/app/components/base/prompt-editor/hooks.ts @@ -154,17 +154,18 @@ type TriggerFn = ( text: string, editor: LexicalEditor, ) => MenuTextMatch | null -const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;' +const escapeForCharacterClass = (value: string) => value.replace(/[[\]\\^-]/g, '\\$&') export function useBasicTypeaheadTriggerMatch( trigger: string, { minLength = 1, maxLength = 75 }: { minLength?: number, maxLength?: number }, ): TriggerFn { return useCallback( (text: string) => { - const validChars = `[${PUNCTUATION}\\s]` + const escapedTrigger = escapeForCharacterClass(trigger) + const validChars = `[^${escapedTrigger}\\n\\r]` const TypeaheadTriggerRegex = new RegExp( '(.*)(' - + `[${trigger}]` + + `[${escapedTrigger}]` + `((?:${validChars}){0,${maxLength}})` + ')$', ) diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/__tests__/index.spec.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/__tests__/index.spec.tsx index 6cc38ad78f..3c734700a7 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/__tests__/index.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/__tests__/index.spec.tsx @@ -521,6 +521,84 @@ describe('ComponentPicker (component-picker-block/index.tsx)', () => { await waitFor(() => expect(readEditorText(editor)).not.toContain('{')) }) + it('filters workflow variables from slash input and matches child paths', async () => { + const captures: Captures = { editor: null, eventEmitter: null } + const user = userEvent.setup() + + const workflowVariableBlock = makeWorkflowVariableBlock({}, [ + makeWorkflowVarNode('node-1', 'Node 1', [ + makeWorkflowNodeVar('payload', VarType.object, [makeWorkflowNodeVar('child_name', VarType.string)]), + makeWorkflowNodeVar('other_value', VarType.string), + ]), + ]) + + render(( + + )) + + const editor = await waitForEditor(captures) + const dispatchSpy = vi.spyOn(editor, 'dispatchCommand') + + await setEditorText(editor, '/child', true) + await flushNextTick() + + expect(screen.queryByPlaceholderText('workflow.common.searchVar')).not.toBeInTheDocument() + expect(await screen.findByText('payload')).toBeInTheDocument() + expect(screen.queryByText('other_value')).not.toBeInTheDocument() + + const label = document.querySelector('[title="payload"]') + expect(label).not.toBeNull() + const row = (label as HTMLElement).parentElement?.parentElement + expect(row).not.toBeNull() + + await user.hover(row as HTMLElement) + const childField = await screen.findByText('child_name') + fireEvent.mouseDown(childField) + await user.unhover(row as HTMLElement) + + expect(dispatchSpy).toHaveBeenCalledWith(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, ['node-1', 'payload', 'child_name']) + await waitFor(() => expect(readEditorText(editor)).not.toContain('/child')) + }) + + it('filters workflow variables on the first character after slash and does not highlight context by default', async () => { + const captures: Captures = { editor: null, eventEmitter: null } + + const workflowVariableBlock = makeWorkflowVariableBlock({}, [ + makeWorkflowVarNode('node-1', 'Node 1', [ + makeWorkflowNodeVar('child_value', VarType.string), + makeWorkflowNodeVar('other_value', VarType.string), + ]), + ]) + + render(( + + )) + + const editor = await waitForEditor(captures) + await setEditorText(editor, '/c', true) + await flushNextTick() + + expect(await screen.findByText('child_value')).toBeInTheDocument() + expect(screen.queryByText('other_value')).not.toBeInTheDocument() + + const contextTitle = screen.getByText('common.promptEditor.context.item.title') + expect(contextTitle.closest('[tabindex="-1"]')).not.toHaveClass('bg-state-base-hover!') + + await waitFor(() => { + expect(readEditorText(editor)).toContain('/c') + }) + }) + it('skips removing the trigger when selection is null (needRemove is null) and still dispatches', async () => { const captures: Captures = { editor: null, eventEmitter: null } diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx index 5903060911..5e983ed09a 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx @@ -1,5 +1,5 @@ import type { MenuRenderFn } from '@lexical/react/LexicalTypeaheadMenuPlugin' -import type { TextNode } from 'lexical' +import type { LexicalEditor, TextNode } from 'lexical' import type { ContextBlockType, CurrentBlockType, @@ -89,10 +89,16 @@ const ComponentPicker = ({ ], }) const [editor] = useLexicalComposerContext() - const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, { + const triggerMatchRef = useRef(null) + const baseCheckForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, { minLength: 0, - maxLength: 0, + maxLength: 75, }) + const checkForTriggerMatch = useCallback((text: string, editor: LexicalEditor) => { + const match = baseCheckForTriggerMatch(text, editor) + triggerMatchRef.current = match?.matchingString ?? null + return match + }, [baseCheckForTriggerMatch]) const [queryString, setQueryString] = useState(null) const [blurHidden, setBlurHidden] = useState(false) @@ -155,6 +161,7 @@ const ComponentPicker = ({ currentBlock, errorMessageBlock, lastRunBlock, + queryString || undefined, ) const onSelectOption = useCallback( @@ -207,6 +214,8 @@ const ComponentPicker = ({ anchorElementRef, { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, ) => { + const effectiveQueryString = triggerMatchRef.current ?? queryString + if (blurHidden) return null if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) @@ -237,6 +246,8 @@ const ComponentPicker = ({ workflowVariableBlock?.show && (
{ @@ -270,8 +281,8 @@ const ComponentPicker = ({ ) } {option.renderMenuOption({ - queryString, - isSelected: selectedIndex === index, + queryString: effectiveQueryString, + isSelected: workflowVariableBlock?.show ? false : selectedIndex === index, onSelect: () => { selectOptionAndCleanUp(option) }, diff --git a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.helpers.spec.ts b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.helpers.spec.ts index 773ecacc39..13b7879bad 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.helpers.spec.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.helpers.spec.ts @@ -81,4 +81,27 @@ describe('var-reference-vars helpers', () => { expect(vars[0]!.title).toBe('Node B') expect(vars[0]!.vars).toEqual([expect.objectContaining({ variable: 'another_value' })]) }) + + it('should keep parent vars when search text matches a child variable', () => { + const vars = filterReferenceVars([ + { + title: 'Node A', + nodeId: 'node-a', + vars: [{ + variable: 'payload', + type: VarType.object, + children: [{ variable: 'child_name', type: VarType.string }], + }], + }, + { + title: 'Node B', + nodeId: 'node-b', + vars: [{ variable: 'other_value', type: VarType.string }], + }, + ] as NodeOutPutVar[], 'child') + + expect(vars).toHaveLength(1) + expect(vars[0]!.title).toBe('Node A') + expect(vars[0]!.vars).toEqual([expect.objectContaining({ variable: 'payload' })]) + }) }) diff --git a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.spec.tsx b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.spec.tsx index 93c857223a..372fcb3508 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-vars.spec.tsx @@ -199,6 +199,34 @@ describe('VarReferenceVars', () => { })) }) + it('should filter by externally controlled search text and match child variables', () => { + render( + , + ) + + expect(screen.queryByPlaceholderText('workflow.common.searchVar')).not.toBeInTheDocument() + expect(screen.getByText('payload')).toBeInTheDocument() + expect(screen.queryByText('other_value')).not.toBeInTheDocument() + }) + it('should ignore file vars when file support is disabled and forward blur-sm events', () => { const onChange = vi.fn() const onBlur = vi.fn() diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.helpers.ts b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.helpers.ts index d36dc807a8..a9941bf72c 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.helpers.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.helpers.ts @@ -1,3 +1,4 @@ +import type { Field, StructuredOutput } from '@/app/components/workflow/nodes/llm/types' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants' import { checkKeys } from '@/utils/var' @@ -76,6 +77,51 @@ const getVisibleChildren = (vars: Var[]) => { return vars.filter(variable => checkKeys([variable.variable], false).isValid || isSpecialVar(variable.variable.split('.')[0]!)) } +const includesSearchText = (value: string | undefined, searchTextLower: string) => { + if (!value) + return false + + return value.toLowerCase().includes(searchTextLower) +} + +const isStructuredOutputChildren = (children: Var['children']): children is StructuredOutput => { + return !!children && !Array.isArray(children) && 'schema' in children +} + +const matchesStructuredField = (fieldName: string, field: Field, searchTextLower: string): boolean => { + if (includesSearchText(fieldName, searchTextLower)) + return true + + if (field.properties) + return Object.entries(field.properties).some(([childName, childField]) => matchesStructuredField(childName, childField, searchTextLower)) + + if (field.items) + return matchesStructuredField(field.items.type, field.items, searchTextLower) + + return false +} + +const matchesVariableSearch = (variable: Var, searchTextLower: string): boolean => { + if ( + includesSearchText(variable.variable, searchTextLower) + || includesSearchText(variable.des, searchTextLower) + || includesSearchText(variable.schemaType, searchTextLower) + ) { + return true + } + + if (!variable.children) + return false + + if (Array.isArray(variable.children)) + return getVisibleChildren(variable.children).some(child => matchesVariableSearch(child, searchTextLower)) + + if (isStructuredOutputChildren(variable.children)) + return Object.entries(variable.children.schema.properties).some(([fieldName, field]) => matchesStructuredField(fieldName, field, searchTextLower)) + + return false +} + export const filterReferenceVars = (vars: NodeOutPutVar[], searchText: string) => { const searchTextLower = searchText.toLowerCase() @@ -85,7 +131,7 @@ export const filterReferenceVars = (vars: NodeOutPutVar[], searchText: string) = .filter((node) => { if (!searchText) return true - return node.vars.some(variable => variable.variable.toLowerCase().includes(searchTextLower)) + return node.vars.some(variable => matchesVariableSearch(variable, searchTextLower)) || node.title.toLowerCase().includes(searchTextLower) }) .map((node) => { @@ -94,7 +140,7 @@ export const filterReferenceVars = (vars: NodeOutPutVar[], searchText: string) = return { ...node, - vars: node.vars.filter(variable => variable.variable.toLowerCase().includes(searchTextLower)), + vars: node.vars.filter(variable => matchesVariableSearch(variable, searchTextLower)), } }) } diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 5c67723d78..648e795dcc 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -241,6 +241,7 @@ const Item: FC = ({ type Props = { hideSearch?: boolean + searchText?: string searchBoxClassName?: string vars: NodeOutPutVar[] isSupportFileVar?: boolean @@ -258,6 +259,7 @@ type Props = { } const VarReferenceVars: FC = ({ hideSearch, + searchText, searchBoxClassName, vars, isSupportFileVar, @@ -274,7 +276,8 @@ const VarReferenceVars: FC = ({ preferSchemaType, }) => { const { t } = useTranslation() - const [searchText, setSearchText] = useState('') + const [internalSearchValue, setInternalSearchValue] = useState('') + const searchValue = searchText ?? internalSearchValue const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { @@ -283,7 +286,7 @@ const VarReferenceVars: FC = ({ } } - const filteredVars = useMemo(() => filterReferenceVars(vars, searchText), [vars, searchText]) + const filteredVars = useMemo(() => filterReferenceVars(vars, searchValue), [vars, searchValue]) return ( <> @@ -295,11 +298,11 @@ const VarReferenceVars: FC = ({ className="var-search-input" showLeftIcon showClearIcon - value={searchText} + value={searchValue} placeholder={t('common.searchVar', { ns: 'workflow' }) || ''} - onChange={e => setSearchText(e.target.value)} + onChange={e => setInternalSearchValue(e.target.value)} onKeyDown={handleKeyDown} - onClear={() => setSearchText('')} + onClear={() => setInternalSearchValue('')} onBlur={onBlur} autoFocus={autoFocus} /> From 73f9a9e7d622931cf87a7a131669621b2fe867fe Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:18:31 +0800 Subject: [PATCH 025/253] chore(deps): bump base ui to 1.4.1 (#35459) --- pnpm-lock.yaml | 26 +++++++++++++++----------- pnpm-workspace.yaml | 2 +- web/eslint.config.mjs | 8 ++++---- web/eslint.constants.mjs | 17 ++++++++++++++++- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c276fae9e..0725b3bb7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,8 +16,8 @@ catalogs: specifier: 8.2.0 version: 8.2.0 '@base-ui/react': - specifier: 1.4.0 - version: 1.4.0 + specifier: 1.4.1 + version: 1.4.1 '@chromatic-com/storybook': specifier: 5.1.2 version: 5.1.2 @@ -643,7 +643,7 @@ importers: devDependencies: '@base-ui/react': specifier: 'catalog:' - version: 1.4.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@chromatic-com/storybook': specifier: 'catalog:' version: 5.1.2(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) @@ -790,7 +790,7 @@ importers: version: 1.27.7(@amplitude/rrweb@2.0.0-alpha.37) '@base-ui/react': specifier: 'catalog:' - version: 1.4.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@emoji-mart/data': specifier: 'catalog:' version: 1.2.1 @@ -1539,8 +1539,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.4.0': - resolution: {integrity: sha512-QcqdVbr/+ba2/RAKJIV1PV6S02Q5+r6a4Eym8ndBw+ZbBILkkmQAyRxXCg/pArrHnkrGeU8goe26aw0h6eE8pg==} + '@base-ui/react@1.4.1': + resolution: {integrity: sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==} engines: {node: '>=14.0.0'} peerDependencies: '@date-fns/tz': ^1.2.0 @@ -1549,11 +1549,15 @@ packages: react: ^17 || ^18 || ^19 react-dom: ^17 || ^18 || ^19 peerDependenciesMeta: + '@date-fns/tz': + optional: true '@types/react': optional: true + date-fns: + optional: true - '@base-ui/utils@0.2.7': - resolution: {integrity: sha512-nXYKhiL/0JafyJE8PfcflipGftOftlIwKd72rU15iZ1M5yqgg5J9P8NHU71GReDuXco5MJA/eVQqUT5WRqX9sA==} + '@base-ui/utils@0.2.8': + resolution: {integrity: sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==} peerDependencies: '@types/react': ^17 || ^18 || ^19 react: ^17 || ^18 || ^19 @@ -8622,10 +8626,10 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@base-ui/react@1.4.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@base-ui/react@1.4.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 - '@base-ui/utils': 0.2.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@base-ui/utils': 0.2.8(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@floating-ui/utils': 0.2.11 react: 19.2.5 @@ -8634,7 +8638,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@base-ui/utils@0.2.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@base-ui/utils@0.2.8(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 '@floating-ui/utils': 0.2.11 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f02d05b233..5afca57719 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -50,7 +50,7 @@ catalog: '@amplitude/analytics-browser': 2.39.0 '@amplitude/plugin-session-replay-browser': 1.27.7 '@antfu/eslint-config': 8.2.0 - '@base-ui/react': 1.4.0 + '@base-ui/react': 1.4.1 '@chromatic-com/storybook': 5.1.2 '@cucumber/cucumber': 12.8.0 '@egoist/tailwindcss-icons': 1.9.2 diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index 62cc63536f..2a043ac73d 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -13,9 +13,9 @@ import storybook from 'eslint-plugin-storybook' import { HYOBAN_PREFER_TAILWIND_ICONS_OPTIONS, NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS, - NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS, OVERLAY_MIGRATION_LEGACY_BASE_FILES, OVERLAY_RESTRICTED_IMPORT_PATTERNS, + WEB_RESTRICTED_IMPORT_PATTERNS, } from './eslint.constants.mjs' import dify from './plugins/eslint/index.js' @@ -161,13 +161,13 @@ export default antfu( }, }, { - name: 'dify/no-direct-next-imports', + name: 'dify/restricted-imports', files: [GLOB_TS, GLOB_TSX], ignores: ['next/**'], rules: { 'no-restricted-imports': ['error', { paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS, - patterns: NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS, + patterns: WEB_RESTRICTED_IMPORT_PATTERNS, }], }, }, @@ -183,7 +183,7 @@ export default antfu( 'no-restricted-imports': ['error', { paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS, patterns: [ - ...NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS, + ...WEB_RESTRICTED_IMPORT_PATTERNS, ...OVERLAY_RESTRICTED_IMPORT_PATTERNS, ], }], diff --git a/web/eslint.constants.mjs b/web/eslint.constants.mjs index 1c09cbcb23..1039a300ec 100644 --- a/web/eslint.constants.mjs +++ b/web/eslint.constants.mjs @@ -5,7 +5,7 @@ export const NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS = [ }, ] -export const NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS = [ +const NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS = [ { group: ['next/image'], message: 'Do not import next/image. Use native img tags instead.', @@ -20,6 +20,21 @@ export const NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS = [ }, ] +const BASE_UI_RESTRICTED_IMPORT_PATTERNS = [ + { + group: [ + '@base-ui/react', + '@base-ui/react/*', + ], + message: 'Do not import Base UI directly in web. Use @langgenius/dify-ui/* primitives instead.', + }, +] + +export const WEB_RESTRICTED_IMPORT_PATTERNS = [ + ...NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS, + ...BASE_UI_RESTRICTED_IMPORT_PATTERNS, +] + export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [ { group: [ From 5e5113e08e52f21e32a9b82fde6c88fd3831864d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:59:59 +0900 Subject: [PATCH 026/253] chore(deps-dev): bump the dev group in /api with 6 updates (#35402) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/pyproject.toml | 12 +++---- api/uv.lock | 80 +++++++++++++++++++++++----------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index b13c744f0a..fbd5d394ad 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -114,10 +114,10 @@ override-dependencies = [ dev = [ "coverage>=7.13.4", "dotenv-linter>=0.7.0", - "faker>=20.1.0", + "faker>=40.15.0", "lxml-stubs>=0.5.1", - "basedpyright>=1.39.0", - "ruff>=0.15.10", + "basedpyright>=1.39.3", + "ruff>=0.15.11", "pytest>=9.0.3", "pytest-benchmark>=5.2.3", "pytest-cov>=7.1.0", @@ -157,14 +157,14 @@ dev = [ "types-tensorflow>=2.18.0.20260408", "types-tqdm>=4.67.3.20260408", "types-ujson>=5.10.0", - "boto3-stubs>=1.42.88", + "boto3-stubs>=1.42.92", "types-jmespath>=1.1.0.20260408", - "hypothesis>=6.151.12", + "hypothesis>=6.152.1", "types_pyOpenSSL>=24.1.0", "types_cffi>=2.0.0.20260408", "types_setuptools>=82.0.0.20260408", "pandas-stubs>=3.0.0", - "scipy-stubs>=1.15.3.0", + "scipy-stubs>=1.17.1.4", "types-python-http-client>=3.3.7.20260408", "import-linter>=2.3", "types-redis>=4.6.0.20241004", diff --git a/api/uv.lock b/api/uv.lock index 40d196a160..bab24a87d2 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -469,14 +469,14 @@ wheels = [ [[package]] name = "basedpyright" -version = "1.39.0" +version = "1.39.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodejs-wheel-binaries" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/f4/4a77cc1ffb3dab7391642cde30163961d8ee973e9e6b6740c7d15aa3d3ba/basedpyright-1.39.0.tar.gz", hash = "sha256:6666f51c378c7ac45877c4c1c7041ee0b5b83d755ebc82f898f47b6fafe0cc4f", size = 25357403, upload-time = "2026-04-01T12:27:41.92Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/19/5a5b9b9197973da732638957be3a65cf514d2f5a4964eeedbf33b6c65bbd/basedpyright-1.39.3.tar.gz", hash = "sha256:2f794e6b5f4260fb89f614ca6cd23c6f305373bb6b50c4ed7794ff2ae647fb14", size = 25503187, upload-time = "2026-04-20T22:14:47.424Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/47/08145d1bcc3083ed20059bdecbde404bd767f91b91e2764ec01cffec9f4b/basedpyright-1.39.0-py3-none-any.whl", hash = "sha256:91b8ad50bc85ee4a985b928f9368c35c99eee5a56c44e99b2442fa12ecc3d670", size = 12353868, upload-time = "2026-04-01T12:27:38.495Z" }, + { url = "https://files.pythonhosted.org/packages/54/5c/f950c1239ad26f3bb453e665428a2cf1893995de725a5eb0b64a2520b366/basedpyright-1.39.3-py3-none-any.whl", hash = "sha256:aba760dc83307727554f936d6b4381caa14482f30dbc2173167710e217c1f7ab", size = 12419181, upload-time = "2026-04-20T22:14:51.975Z" }, ] [[package]] @@ -618,15 +618,15 @@ wheels = [ [[package]] name = "boto3-stubs" -version = "1.42.88" +version = "1.42.92" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore-stubs" }, { name = "types-s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/c7/d4dfbb4757cd72fd350ba666902ec3ac19e04d6be639e96cdad4543d4726/boto3_stubs-1.42.88.tar.gz", hash = "sha256:85215fb4938a94d1cf83cd8632f46ae7728b5ec88187d83468f393bbe64236d6", size = 102495, upload-time = "2026-04-10T19:55:57.526Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/b4/7f472d64a89f6aa6b8e8eeadc876667b7e4edfb526c6118efe2b2c98ba17/boto3_stubs-1.42.92.tar.gz", hash = "sha256:4bc934069c5e8c7b3cdd2442569dae14e8272fe207d445bd38aa578b8463638f", size = 102696, upload-time = "2026-04-20T19:55:19.858Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/6f/3befd72080aedbb4ad26b353a6e364645668664930ce49668fd0bab8f2b5/boto3_stubs-1.42.88-py3-none-any.whl", hash = "sha256:9e74350715ca8ccd63fc250f8eca9fa3161b3d1704339554344d72e4e21c5ed1", size = 70603, upload-time = "2026-04-10T19:55:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ce/2fe2c6456f8dc0b8bb8d80e05e154c7975ec058991bedf54f3aeed634b79/boto3_stubs-1.42.92-py3-none-any.whl", hash = "sha256:b3994e60f0133b2dd3d9a88ceaeef48fa6367d9a9429426e919575768a1ad9c6", size = 70666, upload-time = "2026-04-20T19:55:16.398Z" }, ] [package.optional-dependencies] @@ -1616,13 +1616,13 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "basedpyright", specifier = ">=1.39.0" }, - { name = "boto3-stubs", specifier = ">=1.42.88" }, + { name = "basedpyright", specifier = ">=1.39.3" }, + { name = "boto3-stubs", specifier = ">=1.42.92" }, { name = "celery-types", specifier = ">=0.23.0" }, { name = "coverage", specifier = ">=7.13.4" }, { name = "dotenv-linter", specifier = ">=0.7.0" }, - { name = "faker", specifier = ">=20.1.0" }, - { name = "hypothesis", specifier = ">=6.151.12" }, + { name = "faker", specifier = ">=40.15.0" }, + { name = "hypothesis", specifier = ">=6.152.1" }, { name = "import-linter", specifier = ">=2.3" }, { name = "lxml-stubs", specifier = ">=0.5.1" }, { name = "mypy", specifier = ">=1.20.1" }, @@ -1635,8 +1635,8 @@ dev = [ { name = "pytest-mock", specifier = ">=3.15.1" }, { name = "pytest-timeout", specifier = ">=2.4.0" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, - { name = "ruff", specifier = ">=0.15.10" }, - { name = "scipy-stubs", specifier = ">=1.15.3.0" }, + { name = "ruff", specifier = ">=0.15.11" }, + { name = "scipy-stubs", specifier = ">=1.17.1.4" }, { name = "testcontainers", specifier = ">=4.14.2" }, { name = "types-aiofiles", specifier = ">=25.1.0" }, { name = "types-beautifulsoup4", specifier = ">=4.12.0" }, @@ -2351,14 +2351,14 @@ wheels = [ [[package]] name = "faker" -version = "40.13.0" +version = "40.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/95/4822ffe94723553789aef783104f4f18fc20d7c4c68e1bbd633e11d09758/faker-40.13.0.tar.gz", hash = "sha256:a0751c84c3abac17327d7bb4c98e8afe70ebf7821e01dd7d0b15cd8856415525", size = 1962043, upload-time = "2026-04-06T16:44:55.68Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/13/6741787bd91c4109c7bed047d68273965cd52ce8a5f773c471b949334b6d/faker-40.15.0.tar.gz", hash = "sha256:20f3a6ec8c266b74d4c554e34118b21c3c2056c0b4a519d15c8decb3a4e6e795", size = 1967447, upload-time = "2026-04-17T20:05:27.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/8a/708103325edff16a0b0e004de0d37db8ba216a32713948c64d71f6d4a4c2/faker-40.13.0-py3-none-any.whl", hash = "sha256:c1298fd0d819b3688fb5fd358c4ba8f56c7c8c740b411fd3dbd8e30bf2c05019", size = 1994597, upload-time = "2026-04-06T16:44:53.698Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a7/a600f8f30d4505e89166de51dd121bd540ab8e560e8cf0901de00a81de8c/faker-40.15.0-py3-none-any.whl", hash = "sha256:71ab3c3370da9d2205ab74ffb0fd51273063ad562b3a3bb69d0026a20923e318", size = 2004447, upload-time = "2026-04-17T20:05:25.437Z" }, ] [[package]] @@ -3317,14 +3317,14 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.151.12" +version = "6.152.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/ab/67ca321d1ab96fd3828b12142f1c258e2d4a668a025d06cd50ab3409787f/hypothesis-6.151.12.tar.gz", hash = "sha256:be485f503979af4c3dfa19e3fc2b967d0458e7f8c4e28128d7e215e0a55102e0", size = 463900, upload-time = "2026-04-08T19:40:06.205Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/b1/c32bcddb9aab9e3abc700f1f56faf14e7655c64a16ca47701a57362276ea/hypothesis-6.152.1.tar.gz", hash = "sha256:4f4ed934eee295dd84ee97592477d23e8dc03e9f12ae0ee30a4e7c9ef3fca3b0", size = 465029, upload-time = "2026-04-14T22:29:24.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/5a/6cecf134b631050a1f8605096adbe812483b60790d951470989d39b56860/hypothesis-6.151.12-py3-none-any.whl", hash = "sha256:37d4f3a768365c30571b11dfd7a6857a12173d933010b2c4ab65619f1b5952c5", size = 529656, upload-time = "2026-04-08T19:40:03.126Z" }, + { url = "https://files.pythonhosted.org/packages/5d/83/860fb3075e00b0fc19a22a2301bc3c96f00437558c3911bdd0a3573a4a53/hypothesis-6.152.1-py3-none-any.whl", hash = "sha256:40a3619d9e0cb97b018857c7986f75cf5de2e5ec0fa8a0b172d00747758f749e", size = 530752, upload-time = "2026-04-14T22:29:20.893Z" }, ] [[package]] @@ -5887,27 +5887,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.10" +version = "0.15.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, - { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, - { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, - { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, - { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, - { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, - { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, - { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, - { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, - { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, - { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, + { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, + { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, + { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, + { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, ] [[package]] @@ -5946,14 +5946,14 @@ wheels = [ [[package]] name = "scipy-stubs" -version = "1.17.1.3" +version = "1.17.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "optype", extra = ["numpy"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/59/59c6cc3f9970154b9ed6b1aff42a0185cdd60cef54adc0404b9e77972221/scipy_stubs-1.17.1.3.tar.gz", hash = "sha256:5eb87a8d23d726706259b012ebe76a4a96a9ae9e141fc59bf55fc8eac2ed9e0f", size = 392185, upload-time = "2026-03-22T22:11:58.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/75/d944a11fca64aa84fbb4bfcf613b758319c6103cb30a304a0e9727009d62/scipy_stubs-1.17.1.4.tar.gz", hash = "sha256:cae00c5207aa62ceb4bcadea202d9fbbf002e958f9e4de981720436b8d5c1802", size = 396980, upload-time = "2026-04-13T11:46:54.528Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/d4/94304532c0a75a55526119043dd44a9bd1541a21e14483cbb54261c527d2/scipy_stubs-1.17.1.3-py3-none-any.whl", hash = "sha256:7b91d3f05aa47da06fbca14eb6c5bb4c28994e9245fd250cc847e375bab31297", size = 597933, upload-time = "2026-03-22T22:11:56.525Z" }, + { url = "https://files.pythonhosted.org/packages/92/f8/334aa5a7a482ea89cb14d92f6a4d9ffa1e193e733144d4d14c7ffcb33583/scipy_stubs-1.17.1.4-py3-none-any.whl", hash = "sha256:e6e5c390fb864745bc3d5f591de81f5cb4f84403857d4f660acb5b6339956f5b", size = 604752, upload-time = "2026-04-13T11:46:53.135Z" }, ] [[package]] From 0fec9af6a6ab587fef032b041ddfd880539c3d7d Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:11:11 +0800 Subject: [PATCH 027/253] chore(deps): bump vite-plus to 0.1.19 (#35462) --- pnpm-lock.yaml | 384 +++++++++++++++--------------- pnpm-workspace.yaml | 10 +- sdks/nodejs-client/vite.config.ts | 1 + 3 files changed, 198 insertions(+), 197 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0725b3bb7d..d573664e3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -532,8 +532,8 @@ catalogs: specifier: 12.0.0-beta.1 version: 12.0.0-beta.1 vite-plus: - specifier: 0.1.18 - version: 0.1.18 + specifier: 0.1.19 + version: 0.1.19 vitest-browser-react: specifier: 2.2.0 version: 2.2.0 @@ -574,8 +574,8 @@ overrides: svgo@>=3.0.0 <3.3.3: 3.3.3 tar@<=7.5.10: 7.5.11 undici@>=7.0.0 <7.24.0: 7.24.0 - vite: npm:@voidzero-dev/vite-plus-core@0.1.18 - vitest: npm:@voidzero-dev/vite-plus-test@0.1.18 + vite: npm:@voidzero-dev/vite-plus-core@0.1.19 + vitest: npm:@voidzero-dev/vite-plus-test@0.1.19 yaml@>=2.0.0 <2.8.3: 2.8.3 yauzl@<3.2.1: 3.2.1 @@ -585,7 +585,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 'catalog:' - version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.20.0))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) eslint: specifier: 'catalog:' version: 10.2.0(jiti@2.6.1) @@ -599,11 +599,11 @@ importers: specifier: 'catalog:' version: 1.3.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.1.18 - version: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) e2e: devDependencies: @@ -626,11 +626,11 @@ importers: specifier: 'catalog:' version: 6.0.2 vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.1.18 - version: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) packages/dify-ui: dependencies: @@ -658,7 +658,7 @@ importers: version: 1.2.10 '@storybook/addon-docs': specifier: 'catalog:' - version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-links': specifier: 'catalog:' version: 10.3.5(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) @@ -667,10 +667,10 @@ importers: version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/react-vite': specifier: 'catalog:' - version: 10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + version: 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.2(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 4.2.2(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) '@types/react': specifier: 'catalog:' version: 19.2.14 @@ -679,10 +679,10 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) class-variance-authority: specifier: 'catalog:' version: 0.7.1 @@ -705,14 +705,14 @@ importers: specifier: 'catalog:' version: 6.0.2 vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.1.18 - version: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) vitest-browser-react: specifier: 'catalog:' - version: 2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) packages/iconify-collections: devDependencies: @@ -736,11 +736,11 @@ importers: specifier: 'catalog:' version: 25.6.0 vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.1.18 - version: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) packages/tsconfig: {} @@ -763,7 +763,7 @@ importers: version: 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) eslint: specifier: 'catalog:' version: 10.2.0(jiti@2.6.1) @@ -771,14 +771,14 @@ importers: specifier: 'catalog:' version: 6.0.2 vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.1.18 - version: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) vitest: - specifier: npm:@voidzero-dev/vite-plus-test@0.1.18 - version: '@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-test@0.1.19 + version: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' web: dependencies: @@ -1091,7 +1091,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 'catalog:' - version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.20.0))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) '@chromatic-com/storybook': specifier: 'catalog:' version: 5.1.2(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) @@ -1139,7 +1139,7 @@ importers: version: 4.2.0 '@storybook/addon-docs': specifier: 'catalog:' - version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-links': specifier: 'catalog:' version: 10.3.5(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) @@ -1151,7 +1151,7 @@ importers: version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/nextjs-vite': specifier: 'catalog:' - version: 10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + version: 10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) '@storybook/react': specifier: 'catalog:' version: 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) @@ -1160,7 +1160,7 @@ importers: version: 4.2.2 '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.2(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 4.2.2(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) '@tanstack/eslint-plugin-query': specifier: 'catalog:' version: 5.99.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) @@ -1226,13 +1226,13 @@ importers: version: 7.0.0-dev.20260413.1 '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.24(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + version: 0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) agentation: specifier: 'catalog:' version: 3.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -1247,7 +1247,7 @@ importers: version: 0.6.1(eslint@10.2.0(jiti@2.6.1)) eslint-plugin-better-tailwindcss: specifier: 'catalog:' - version: 4.4.1(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.20.0))(tailwindcss@4.2.2)(typescript@6.0.2) + version: 4.4.1(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tailwindcss@4.2.2)(typescript@6.0.2) eslint-plugin-hyoban: specifier: 'catalog:' version: 0.14.1(eslint@10.2.0(jiti@2.6.1)) @@ -1298,22 +1298,22 @@ importers: version: 3.19.3 vinext: specifier: 'catalog:' - version: 0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + version: 0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.2) vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.1.18 - version: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vite-plugin-inspect: specifier: 'catalog:' - version: 12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0) + version: 12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0) vite-plus: specifier: 'catalog:' - version: 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) vitest: - specifier: npm:@voidzero-dev/vite-plus-test@0.1.18 - version: '@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + specifier: npm:@voidzero-dev/vite-plus-test@0.1.19 + version: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vitest-canvas-mock: specifier: 'catalog:' - version: 1.1.4(@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 1.1.4(@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) packages: @@ -2665,15 +2665,15 @@ packages: cpu: [x64] os: [win32] - '@oxc-project/runtime@0.124.0': - resolution: {integrity: sha512-sSg6n37J3w3mM4odFvRqzQENf6+qxKnvStr/gU0FgRRg1VE/4MqryLd9PJmE0a7K5xlDfbrctBtSagaFH6ij9Q==} + '@oxc-project/runtime@0.126.0': + resolution: {integrity: sha512-oksjxfqDNmIYMGlIgLzYgnz5YjZax27RtQezsPpKEGo9AC5LOaIGHsivCCeaAWdCtPnRyjZXM/7svreCC8kZVQ==} engines: {node: ^20.19.0 || >=22.12.0} '@oxc-project/types@0.121.0': resolution: {integrity: sha512-CGtOARQb9tyv7ECgdAlFxi0Fv7lmzvmlm2rpD/RdijOO9rfk/JvB1CjT8EnoD+tjna/IYgKKw3IV7objRb+aYw==} - '@oxc-project/types@0.124.0': - resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@oxc-project/types@0.126.0': + resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} @@ -2905,33 +2905,33 @@ packages: cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.20.0': - resolution: {integrity: sha512-KKQcIHZHMxqpHUA1VXIbOG6chNCFkUWbQy6M+AFVtPKkA/3xAeJkJ3njoV66bfzwPHRcWQO+kcj5XqtbkjakoA==} + '@oxlint-tsgolint/darwin-arm64@0.21.1': + resolution: {integrity: sha512-7TLjyWe4wG9saJc992VWmaHq2hwKfOEEVTjheReXJXaDhavMZI4X9a6nKhbEng4IVkYtzjD2jw16vw2WFXLYLw==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.20.0': - resolution: {integrity: sha512-7HeVMuclGfG+NLZi2ybY0T4fMI7/XxO/208rJk+zEIloKkVnlh11Wd241JMGwgNFXn+MLJbOqOfojDb2Dt4L1g==} + '@oxlint-tsgolint/darwin-x64@0.21.1': + resolution: {integrity: sha512-7wf9Wf75nTzA7zpL9myhFe2RKvfuqGUOADNvUooCjEWvh7hmPz3lSEqTMh5Z/VQhzsG04mM9ACyghxhRzq7zFw==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.20.0': - resolution: {integrity: sha512-zxhUwz+WSxE6oWlZLK2z2ps9yC6ebmgoYmjAl0Oa48+GqkZ56NVgo+wb8DURNv6xrggzHStQxqQxe3mK51HZag==} + '@oxlint-tsgolint/linux-arm64@0.21.1': + resolution: {integrity: sha512-IPuQN/Vd0Rjklg/cCGBbQyUuRBp2f6LQXpZYwk5ivOR6V/+CgiYsv8pn/PVY7gjeyoNvPQrXB7xMjHUO2YZbdw==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.20.0': - resolution: {integrity: sha512-/1l6FnahC9im8PK+Ekkx/V3yetO/PzZnJegE2FXcv/iXEhbeVxP/ouiTYcUQu9shT1FWJCSNti1VJHH+21Y1dg==} + '@oxlint-tsgolint/linux-x64@0.21.1': + resolution: {integrity: sha512-d1niGuTbh2qiv7dR7tqkbOcM5cIR63of0lMBFdEQavL1KrJV8zuRdwdi68K7MNGdgoR+J5A9ajpGGvsHwp1bPg==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.20.0': - resolution: {integrity: sha512-oPZ5Yz8sVdo7P/5q+i3IKeix31eFZ55JAPa1+RGPoe9PoaYVsdMvR6Jvib6YtrqoJnFPlg3fjEjlEPL8VBKYJA==} + '@oxlint-tsgolint/win32-arm64@0.21.1': + resolution: {integrity: sha512-ICu9y2JLnFPvFqstnWPPNqBM8LK8BWw2OTeaR0UgEMm4hOSbrZAKv1/hwZYyiLqnCNjBL87AGSQIgTHCYlsipw==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.20.0': - resolution: {integrity: sha512-4stx8RHj3SP9vQyRF/yZbz5igtPvYMEUR8CUoha4BVNZihi39DpCR8qkU7lpjB5Ga1DRMo2pHaA4bdTOMaY4mw==} + '@oxlint-tsgolint/win32-x64@0.21.1': + resolution: {integrity: sha512-cTEFCFjCj6iXfrSHcvajSPNqhEA4TxSzU3gFxbdGSAUTNXGToU99IbdhWAPSbhcucoym0XE4Zl7E41NiSkNTug==} cpu: [x64] os: [win32] @@ -4336,13 +4336,13 @@ packages: '@vitest/utils@4.1.4': resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} - '@voidzero-dev/vite-plus-core@0.1.18': - resolution: {integrity: sha512-3PmXOL26yHzlw8ET9SwXCmglGzUYq2fOTYf2t0mxvVIs7ua3bnf6tOnmR+6YX5k1Ez26B0ooYzx+znc8k+CAMw==} + '@voidzero-dev/vite-plus-core@0.1.19': + resolution: {integrity: sha512-BTmz50juSDolIN4Vtu5iVaPONV1XSrMB5V+9IoBhhxdogfvp7PBhaHuAcPjTN2RTVowhLZXoo8mn+aHjq//bkw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.21.8 - '@tsdown/exe': 0.21.8 + '@tsdown/css': 0.21.9 + '@tsdown/exe': 0.21.9 '@types/node': ^20.19.0 || >=22.12.0 '@vitejs/devtools': ^0.1.0 esbuild: 0.27.2 @@ -4396,48 +4396,48 @@ packages: yaml: optional: true - '@voidzero-dev/vite-plus-darwin-arm64@0.1.18': - resolution: {integrity: sha512-bw2pWWE8RZRELWjXcdxdmRaOaYjmGmsxEm23TxvGxQXFb7k9l51W8tpjxariPGLxrEl+Cw5u601IL5LASaPJ5w==} + '@voidzero-dev/vite-plus-darwin-arm64@0.1.19': + resolution: {integrity: sha512-6MY/RiaRXKJ6wD/ftZnf+ohEqU68zHp3bVWetIw9dakcPL7TXoiIkDoechmZXCh+5eqxehvap4eh2eNEvWSM1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@voidzero-dev/vite-plus-darwin-x64@0.1.18': - resolution: {integrity: sha512-8TFj6yJNsumoH+yFc+6zf3g2UuzvrPHq2FAAVORffaVZ29PWnDSsXjegaIBmoAtGO5Xb4lcilQx7NoF9hONrZg==} + '@voidzero-dev/vite-plus-darwin-x64@0.1.19': + resolution: {integrity: sha512-jV6ygWCarMFW5DRqRyFkB2jpRDiAlLYzyQu0HZfYNoxfdNyO7isfuR5X6gV+ji7J3Kp0RZOiGrQUCjxTPqZg5w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.18': - resolution: {integrity: sha512-xHRqncKanOZ0zNnZSufL4Yx/gWrIFkCjU6jFzCukBOOCrcemq3SrALPHrNf+Nw1RLwNptGUZn2Vx/IjRLzUQDw==} + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.19': + resolution: {integrity: sha512-jIWMgAok77aDuTK2kCQXn4Zp7pnUM56BvKhHCvnAmsF4yrs1KLQfH6YOdQMnVbNjQDneQgqdwHVDnkOfJRokYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-arm64-musl@0.1.18': - resolution: {integrity: sha512-CA6XxZbkT8lYwWzS2yAj6exr7nHl3R8Sz+ZdOhYCU4yR2qvzGatdVgFr7oPnrkHLF426cHJ172rmNNj8NKie/w==} + '@voidzero-dev/vite-plus-linux-arm64-musl@0.1.19': + resolution: {integrity: sha512-fUuXUqCl3zMbS5QpMJzewVjrpbtzlwuzYQSh5q59CMq65uCXT07amJzmuAFReDEMrwEAmjGgbamJ1ctLAYCxrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.18': - resolution: {integrity: sha512-xBO3MtLGVASPjH/GDRxexfLCT0othVpiFMdEQ83Y+woVNbrrzcdQTGFUuFG4cAiMhtmjytyFwPBtZ76BWsDO3w==} + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.19': + resolution: {integrity: sha512-xFVGMo1Yo5p9gABpOSSGgu5LhhMQs6qVXU7xL+NAGnaVViAYujNuOhCpBk2yK4Cy98KiNOjwnR5jG0TnRd22xg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-x64-musl@0.1.18': - resolution: {integrity: sha512-ADNis6SMarY7i8+b2ynUJ1PiqCHqnVwY7EQ+fSGug5zZ+W/cZq14+VWPxOvGR9LJk+iol8XuqsHy4BaV2+gjzw==} + '@voidzero-dev/vite-plus-linux-x64-musl@0.1.19': + resolution: {integrity: sha512-iEDxL85v/C01yF2EJKknkjDhKbgY10NL9/sZ4HxezWykePK6QpYY5ClWGL7gIi+YFp8rtAdRPKlrf0mTlYMvxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-test@0.1.18': - resolution: {integrity: sha512-dovC2kJgiwMI8ay0i+3NvQGCDWPj8HQB2ONP/HbdJ5/XQVPq13+BihnCq8/ztz6uGhiDD8Nu4OZ3RgB14uvTfA==} + '@voidzero-dev/vite-plus-test@0.1.19': + resolution: {integrity: sha512-KK0lfqyiEOEykp3hrcHT49f1j3M3t15ZKCuO+e9KbDRambU7tdz70xoHCKkRXcFgnds9gqi09PSLVy1k8XN+Hg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@edge-runtime/vm': '*' @@ -4467,14 +4467,14 @@ packages: jsdom: optional: true - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.18': - resolution: {integrity: sha512-EcDETMHG8xgjIlMizIu/wf0UtRZLGz+lHFvYFZVCkz4vLLz93a06vZ+3Oi9xY2Kc8aOHsCf8Gj5/dox/03cscw==} + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.19': + resolution: {integrity: sha512-2GGeGr2mtXLjV9O8CXEEZkV6O8q8rMBhq8fj5fyaSuBe5FQ1OxGYYMDqNBxvbg+hSUw0ThKK6qmirj5fF2e/iw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.18': - resolution: {integrity: sha512-jBgL4ZjSJJu3FDcrqj4muzbr0WKlU6Ym1ilHQnq8R+2TRvE0AtvAMMuphICDslZGi6EK3fwJ+r2Lv7GU1AipQA==} + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.19': + resolution: {integrity: sha512-//xUNHQnd+p4Xd4rlObAvum3DW1ugbWZ+kfaqD7biHQ9HQwHF28WSpJ3+d31vLUHj4o3DXYSA67g1Bq2d4tVgg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -6797,8 +6797,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint-tsgolint@0.20.0: - resolution: {integrity: sha512-/Uc9TQyN1l8w9QNvXtVHYtz+SzDJHKpb5X0UnHodl0BVzijUPk0LPlDOHAvogd1UI+iy9ZSF6gQxEqfzUxCULQ==} + oxlint-tsgolint@0.21.1: + resolution: {integrity: sha512-O2hxiT14C2HJkwzBU6CQBFPoagSd/IcV+Tt3e3UUaXFwbW4BO5DSDPSSboc3UM5MIDY+MLyepvtQwBQafNxWdw==} hasBin: true oxlint@1.60.0: @@ -8039,8 +8039,8 @@ 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 || ^10.4.0-0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vite-plus@0.1.18: - resolution: {integrity: sha512-RiWUoOmQiJMtd4Dfm6WD0v0Selqh/nQzmaGVIrkfnr+2s5UxGVZy7n2TCO5ZnR7w9noMIgtUAQN8GtKhwHEiOQ==} + vite-plus@0.1.19: + resolution: {integrity: sha512-QWuTqkO/a8Q7I3hHnYdvwlJa7mcc6hgh99/8CHoRb27pgo+z1ux+NGYYCZPJHKVtatAtVRaQQvy4cEQBHyB87A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -8437,17 +8437,17 @@ snapshots: idb: 8.0.0 tslib: 2.8.1 - '@antfu/eslint-config@8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.20.0))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@antfu/eslint-config@8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 1.2.0 - '@e18e/eslint-plugin': 0.3.0(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.20.0)) + '@e18e/eslint-plugin': 0.3.0(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1)) '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.2.0(jiti@2.6.1)) '@eslint/markdown': 8.0.1 '@stylistic/eslint-plugin': 5.10.0(eslint@10.2.0(jiti@2.6.1)) '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/parser': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@vitest/eslint-plugin': 1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@vitest/eslint-plugin': 1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) ansis: 4.2.0 cac: 7.0.0 eslint: 10.2.0(jiti@2.6.1) @@ -8857,12 +8857,12 @@ snapshots: '@cucumber/tag-expressions@9.1.0': {} - '@e18e/eslint-plugin@0.3.0(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.20.0))': + '@e18e/eslint-plugin@0.3.0(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))': dependencies: eslint-plugin-depend: 1.5.0(eslint@10.2.0(jiti@2.6.1)) optionalDependencies: eslint: 10.2.0(jiti@2.6.1) - oxlint: 1.60.0(oxlint-tsgolint@0.20.0) + oxlint: 1.60.0(oxlint-tsgolint@0.21.1) '@egoist/tailwindcss-icons@1.9.2(tailwindcss@4.2.2)': dependencies: @@ -9390,11 +9390,11 @@ snapshots: dependencies: minipass: 7.1.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@6.0.2) - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' optionalDependencies: typescript: 6.0.2 @@ -9831,11 +9831,11 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc@0.121.0': optional: true - '@oxc-project/runtime@0.124.0': {} + '@oxc-project/runtime@0.126.0': {} '@oxc-project/types@0.121.0': {} - '@oxc-project/types@0.124.0': {} + '@oxc-project/types@0.126.0': {} '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -9959,22 +9959,22 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.45.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.20.0': + '@oxlint-tsgolint/darwin-arm64@0.21.1': optional: true - '@oxlint-tsgolint/darwin-x64@0.20.0': + '@oxlint-tsgolint/darwin-x64@0.21.1': optional: true - '@oxlint-tsgolint/linux-arm64@0.20.0': + '@oxlint-tsgolint/linux-arm64@0.21.1': optional: true - '@oxlint-tsgolint/linux-x64@0.20.0': + '@oxlint-tsgolint/linux-x64@0.21.1': optional: true - '@oxlint-tsgolint/win32-arm64@0.20.0': + '@oxlint-tsgolint/win32-arm64@0.21.1': optional: true - '@oxlint-tsgolint/win32-x64@0.20.0': + '@oxlint-tsgolint/win32-x64@0.21.1': optional: true '@oxlint/binding-android-arm-eabi@1.60.0': @@ -10474,10 +10474,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) - '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) react: 19.2.5 @@ -10507,24 +10507,24 @@ snapshots: storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/builder-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: - '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/csf-plugin@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' '@storybook/global@5.0.0': {} @@ -10533,18 +10533,18 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - '@storybook/nextjs-vite@10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2)': + '@storybook/nextjs-vite@10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2)': dependencies: - '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) - '@storybook/react-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + '@storybook/react-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) next: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 react-dom: 19.2.5(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5) - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - vite-plugin-storybook-nextjs: 3.2.4(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite-plugin-storybook-nextjs: 3.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) optionalDependencies: typescript: 6.0.2 transitivePeerDependencies: @@ -10561,11 +10561,11 @@ snapshots: react-dom: 19.2.5(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@storybook/react-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2)': + '@storybook/react-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2)': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) '@rollup/pluginutils': 5.3.0 - '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) empathic: 2.0.0 magic-string: 0.30.21 @@ -10575,7 +10575,7 @@ snapshots: resolve: 1.22.11 storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tsconfig-paths: 4.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' transitivePeerDependencies: - esbuild - rollup @@ -10714,12 +10714,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.2 - '@tailwindcss/vite@4.2.2(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.2(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))': dependencies: '@tailwindcss/node': 4.2.2 '@tailwindcss/oxide': 4.2.2 tailwindcss: 4.2.2 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' '@tanstack/devtools-client@0.0.6': dependencies: @@ -11337,12 +11337,12 @@ snapshots: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vitejs/devtools-kit@0.1.11(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0)': + '@vitejs/devtools-kit@0.1.11(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0)': dependencies: '@vitejs/devtools-rpc': 0.1.11(typescript@6.0.2)(ws@8.20.0) birpc: 4.0.0 ohash: 2.0.11 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' transitivePeerDependencies: - typescript - ws @@ -11359,12 +11359,12 @@ snapshots: transitivePeerDependencies: - typescript - '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))': + '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - '@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)': + '@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)': dependencies: '@rolldown/pluginutils': 1.0.0-rc.15 es-module-lexer: 2.0.0 @@ -11375,12 +11375,12 @@ snapshots: srvx: 0.11.15 strip-literal: 3.1.0 turbo-stream: 3.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - vitefu: 1.1.3(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitefu: 1.1.3(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) optionalDependencies: react-server-dom-webpack: 19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.4 @@ -11392,7 +11392,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: '@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -11422,12 +11422,12 @@ snapshots: - vite - yaml - '@vitest/eslint-plugin@1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@vitest/eslint-plugin@1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) eslint: 10.2.0(jiti@2.6.1) - vitest: '@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' optionalDependencies: '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) typescript: 6.0.2 @@ -11493,10 +11493,10 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': dependencies: - '@oxc-project/runtime': 0.124.0 - '@oxc-project/types': 0.124.0 + '@oxc-project/runtime': 0.126.0 + '@oxc-project/types': 0.126.0 lightningcss: 1.32.0 postcss: 8.5.9 optionalDependencies: @@ -11508,29 +11508,29 @@ snapshots: typescript: 6.0.2 yaml: 2.8.3 - '@voidzero-dev/vite-plus-darwin-arm64@0.1.18': + '@voidzero-dev/vite-plus-darwin-arm64@0.1.19': optional: true - '@voidzero-dev/vite-plus-darwin-x64@0.1.18': + '@voidzero-dev/vite-plus-darwin-x64@0.1.19': optional: true - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.18': + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.19': optional: true - '@voidzero-dev/vite-plus-linux-arm64-musl@0.1.18': + '@voidzero-dev/vite-plus-linux-arm64-musl@0.1.19': optional: true - '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.18': + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.19': optional: true - '@voidzero-dev/vite-plus-linux-x64-musl@0.1.18': + '@voidzero-dev/vite-plus-linux-x64-musl@0.1.19': optional: true - '@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@voidzero-dev/vite-plus-core': 0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) es-module-lexer: 1.7.0 obug: 2.1.1 pixelmatch: 7.1.0 @@ -11540,11 +11540,11 @@ snapshots: tinybench: 2.9.0 tinyexec: 1.0.4 tinyglobby: 0.2.16 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' ws: 8.20.0 optionalDependencies: '@types/node': 25.6.0 - '@vitest/coverage-v8': 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@vitest/coverage-v8': 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) happy-dom: 20.9.0 transitivePeerDependencies: - '@arethetypeswrong/core' @@ -11567,10 +11567,10 @@ snapshots: - utf-8-validate - yaml - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.18': + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.19': optional: true - '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.18': + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.19': optional: true '@volar/language-core@2.4.28': @@ -12467,7 +12467,7 @@ snapshots: dependencies: eslint: 10.2.0(jiti@2.6.1) - eslint-plugin-better-tailwindcss@4.4.1(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.20.0))(tailwindcss@4.2.2)(typescript@6.0.2): + eslint-plugin-better-tailwindcss@4.4.1(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tailwindcss@4.2.2)(typescript@6.0.2): dependencies: '@eslint/css-tree': 4.0.1 '@valibot/to-json-schema': 1.6.0(valibot@1.3.1(typescript@6.0.2)) @@ -12480,7 +12480,7 @@ snapshots: valibot: 1.3.1(typescript@6.0.2) optionalDependencies: eslint: 10.2.0(jiti@2.6.1) - oxlint: 1.60.0(oxlint-tsgolint@0.20.0) + oxlint: 1.60.0(oxlint-tsgolint@0.21.1) transitivePeerDependencies: - '@eslint/css' - typescript @@ -14492,16 +14492,16 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.45.0 '@oxfmt/binding-win32-x64-msvc': 0.45.0 - oxlint-tsgolint@0.20.0: + oxlint-tsgolint@0.21.1: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.20.0 - '@oxlint-tsgolint/darwin-x64': 0.20.0 - '@oxlint-tsgolint/linux-arm64': 0.20.0 - '@oxlint-tsgolint/linux-x64': 0.20.0 - '@oxlint-tsgolint/win32-arm64': 0.20.0 - '@oxlint-tsgolint/win32-x64': 0.20.0 + '@oxlint-tsgolint/darwin-arm64': 0.21.1 + '@oxlint-tsgolint/darwin-x64': 0.21.1 + '@oxlint-tsgolint/linux-arm64': 0.21.1 + '@oxlint-tsgolint/linux-x64': 0.21.1 + '@oxlint-tsgolint/win32-arm64': 0.21.1 + '@oxlint-tsgolint/win32-x64': 0.21.1 - oxlint@1.60.0(oxlint-tsgolint@0.20.0): + oxlint@1.60.0(oxlint-tsgolint@0.21.1): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.60.0 '@oxlint/binding-android-arm64': 1.60.0 @@ -14522,7 +14522,7 @@ snapshots: '@oxlint/binding-win32-arm64-msvc': 1.60.0 '@oxlint/binding-win32-ia32-msvc': 1.60.0 '@oxlint/binding-win32-x64-msvc': 1.60.0 - oxlint-tsgolint: 0.20.0 + oxlint-tsgolint: 0.21.1 p-limit@3.1.0: dependencies: @@ -15826,20 +15826,20 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.2): + vinext@0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.2): dependencies: '@unpic/react': 1.0.2(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@vercel/og': 0.8.6 - '@vitejs/plugin-react': 6.0.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + '@vitejs/plugin-react': 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) magic-string: 0.30.21 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' vite-plugin-commonjs: 0.10.4 - vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) + vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) optionalDependencies: '@mdx-js/rollup': 3.1.1 - '@vitejs/plugin-rsc': 0.5.24(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + '@vitejs/plugin-rsc': 0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) react-server-dom-webpack: 19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) transitivePeerDependencies: - next @@ -15859,9 +15859,9 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 - vite-plugin-inspect@12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0): + vite-plugin-inspect@12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0): dependencies: - '@vitejs/devtools-kit': 0.1.11(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0) + '@vitejs/devtools-kit': 0.1.11(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0) ansis: 4.2.0 error-stack-parser-es: 1.0.5 obug: 2.1.1 @@ -15870,12 +15870,12 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' transitivePeerDependencies: - typescript - ws - vite-plugin-storybook-nextjs@3.2.4(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2): + vite-plugin-storybook-nextjs@3.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2): dependencies: '@next/env': 16.0.0 image-size: 2.0.2 @@ -15884,29 +15884,29 @@ snapshots: next: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) transitivePeerDependencies: - supports-color - typescript - vite-plus@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3): + vite-plus@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3): dependencies: - '@oxc-project/types': 0.124.0 - '@voidzero-dev/vite-plus-core': 0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) - '@voidzero-dev/vite-plus-test': 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@oxc-project/types': 0.126.0 + '@voidzero-dev/vite-plus-core': 0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@voidzero-dev/vite-plus-test': 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) oxfmt: 0.45.0 - oxlint: 1.60.0(oxlint-tsgolint@0.20.0) - oxlint-tsgolint: 0.20.0 + oxlint: 1.60.0(oxlint-tsgolint@0.21.1) + oxlint-tsgolint: 0.21.1 optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.18 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.18 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.18 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.18 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.18 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.18 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.18 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.18 + '@voidzero-dev/vite-plus-darwin-arm64': 0.1.19 + '@voidzero-dev/vite-plus-darwin-x64': 0.1.19 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.19 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.19 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.19 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.19 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.19 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.19 transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -15937,36 +15937,36 @@ snapshots: - vite - yaml - vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2): + vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2): dependencies: debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.2) optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2): + vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2): dependencies: debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.2) - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' transitivePeerDependencies: - supports-color - typescript - vitefu@1.1.3(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)): + vitefu@1.1.3(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)): optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - vitest-browser-react@2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3): + vitest-browser-react@2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3): dependencies: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - vitest: '@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) @@ -16000,11 +16000,11 @@ snapshots: - vite - yaml - vitest-canvas-mock@1.1.4(@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)): + vitest-canvas-mock@1.1.4(@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: '@voidzero-dev/vite-plus-test@0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' void-elements@3.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5afca57719..7a81789267 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -42,8 +42,8 @@ overrides: svgo@>=3.0.0 <3.3.3: 3.3.3 tar@<=7.5.10: 7.5.11 undici@>=7.0.0 <7.24.0: 7.24.0 - vite: npm:@voidzero-dev/vite-plus-core@0.1.18 - vitest: npm:@voidzero-dev/vite-plus-test@0.1.18 + vite: npm:@voidzero-dev/vite-plus-core@0.1.19 + vitest: npm:@voidzero-dev/vite-plus-test@0.1.19 yaml@>=2.0.0 <2.8.3: 2.8.3 yauzl@<3.2.1: 3.2.1 catalog: @@ -222,10 +222,10 @@ catalog: use-context-selector: 2.0.0 uuid: 13.0.0 vinext: 0.0.41 - vite: npm:@voidzero-dev/vite-plus-core@0.1.18 + vite: npm:@voidzero-dev/vite-plus-core@0.1.19 vite-plugin-inspect: 12.0.0-beta.1 - vite-plus: 0.1.18 - vitest: npm:@voidzero-dev/vite-plus-test@0.1.18 + vite-plus: 0.1.19 + vitest: npm:@voidzero-dev/vite-plus-test@0.1.19 vitest-browser-react: 2.2.0 vitest-canvas-mock: 1.1.4 zod: 4.3.6 diff --git a/sdks/nodejs-client/vite.config.ts b/sdks/nodejs-client/vite.config.ts index 8d89508682..68ced551a3 100644 --- a/sdks/nodejs-client/vite.config.ts +++ b/sdks/nodejs-client/vite.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ pack: { entry: ["src/index.ts"], format: ["esm"], + platform: "node", dts: true, clean: true, sourcemap: true, From 44a91e344cee4885124a0464db402f18efadf44d Mon Sep 17 00:00:00 2001 From: zyssyz123 <916125788@qq.com> Date: Tue, 21 Apr 2026 17:28:44 +0800 Subject: [PATCH 028/253] fix(plugin): persist tenant plugin auto-upgrade strategy changes (#35464) --- api/services/plugin/plugin_auto_upgrade_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/services/plugin/plugin_auto_upgrade_service.py b/api/services/plugin/plugin_auto_upgrade_service.py index 9bb0ab6ae2..b96b8140ac 100644 --- a/api/services/plugin/plugin_auto_upgrade_service.py +++ b/api/services/plugin/plugin_auto_upgrade_service.py @@ -23,7 +23,7 @@ class PluginAutoUpgradeService: exclude_plugins: list[str], include_plugins: list[str], ) -> bool: - with session_factory.create_session() as session: + with session_factory.create_session() as session, session.begin(): exist_strategy = session.scalar( select(TenantPluginAutoUpgradeStrategy) .where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) @@ -50,7 +50,7 @@ class PluginAutoUpgradeService: @staticmethod def exclude_plugin(tenant_id: str, plugin_id: str) -> bool: - with session_factory.create_session() as session: + with session_factory.create_session() as session, session.begin(): exist_strategy = session.scalar( select(TenantPluginAutoUpgradeStrategy) .where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) From d65a6b481066cb0abd46a4d376795060e555fc06 Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Tue, 21 Apr 2026 18:09:22 +0800 Subject: [PATCH 029/253] refactor: migrate from PortalToFollowElem to Popover component across various components (#35454) Co-authored-by: CodingOnStar Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 115 ----- .../context-var/__tests__/index.spec.tsx | 84 +++- .../context-var/__tests__/var-picker.spec.tsx | 42 +- .../dataset-config/context-var/var-picker.tsx | 91 ++-- .../member-selector.tsx | 77 +-- .../__tests__/selector-entry.spec.tsx | 55 ++- .../subscription-list/selector-entry.tsx | 53 +-- .../plugin-tasks/__tests__/index.spec.tsx | 25 +- .../plugin-page/plugin-tasks/index.tsx | 1 + .../__tests__/index.spec.tsx | 64 ++- .../__tests__/tool-picker.spec.tsx | 75 +-- .../auto-update-setting/tool-picker.tsx | 128 ++--- .../tools/labels/__tests__/selector.spec.tsx | 59 +++ web/app/components/tools/labels/selector.tsx | 63 +-- .../workflow/__tests__/custom-edge.spec.tsx | 4 + .../__tests__/tool-picker.spec.tsx | 4 +- .../workflow/block-selector/tool-picker.tsx | 72 +-- web/app/components/workflow/custom-edge.tsx | 8 +- .../agent-strategy-selector.spec.tsx | 446 ++++++++++++++++++ .../components/agent-strategy-selector.tsx | 164 ++++--- .../var-reference-picker.trigger.spec.tsx | 146 +++--- .../variable/var-reference-picker.trigger.tsx | 85 ++-- .../variable/var-reference-picker.tsx | 58 ++- .../variable/var-reference-vars.tsx | 128 ++--- .../workflow/nodes/code/dependency-picker.tsx | 41 +- .../__tests__/member-selector.spec.tsx | 53 +++ .../delivery-method/recipient/email-input.tsx | 61 ++- .../recipient/member-selector.tsx | 59 ++- .../if-else/components/condition-add.tsx | 60 +-- .../condition-list/condition-var-selector.tsx | 49 +- .../components/metadata/add-condition.tsx | 89 ++-- .../condition-common-variable-selector.tsx | 86 ++-- .../condition-variable-selector.tsx | 57 +-- .../nodes/loop/components/condition-add.tsx | 60 +-- .../condition-list/condition-var-selector.tsx | 49 +- .../__tests__/search-input.spec.tsx | 159 +++++++ web/app/education-apply/search-input.tsx | 102 ++-- 37 files changed, 1900 insertions(+), 1072 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx create mode 100644 web/app/education-apply/__tests__/search-input.spec.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index d969e0bf83..0e0970f90d 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -488,11 +488,6 @@ "count": 1 } }, - "web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/configuration/dataset-config/index.tsx": { "ts/no-explicit-any": { "count": 1 @@ -3060,14 +3055,6 @@ "count": 3 } }, - "web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 2 - } - }, "web/app/components/header/account-setting/model-provider-page/declarations.ts": { "erasable-syntax-only/enums": { "count": 11 @@ -3554,11 +3541,6 @@ "count": 2 } }, - "web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx": { "no-restricted-imports": { "count": 1 @@ -3672,11 +3654,6 @@ "count": 1 } }, - "web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/plugins/reference-setting-modal/auto-update-setting/types.ts": { "erasable-syntax-only/enums": { "count": 2 @@ -3933,11 +3910,6 @@ "count": 1 } }, - "web/app/components/tools/labels/selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/tools/mcp/create-card.tsx": { "ts/no-explicit-any": { "count": 1 @@ -4123,11 +4095,6 @@ "count": 1 } }, - "web/app/components/workflow/block-selector/tool-picker.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx": { "ts/no-explicit-any": { "count": 1 @@ -4313,14 +4280,6 @@ "count": 2 } }, - "web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx": { - "no-restricted-imports": { - "count": 3 - }, - "ts/no-explicit-any": { - "count": 4 - } - }, "web/app/components/workflow/nodes/_base/components/agent-strategy.tsx": { "ts/no-empty-object-type": { "count": 1 @@ -4547,22 +4506,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "react/set-state-in-effect": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 3 - } - }, - "web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx": { "no-restricted-imports": { "count": 1 @@ -4742,11 +4685,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/code/dependency-picker.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/code/types.ts": { "erasable-syntax-only/enums": { "count": 1 @@ -4897,16 +4835,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-input.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/human-input/components/delivery-method/test-email-sender.tsx": { "no-restricted-imports": { "count": 1 @@ -4957,11 +4885,6 @@ "count": 2 } }, - "web/app/components/workflow/nodes/if-else/components/condition-add.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx": { "ts/no-explicit-any": { "count": 1 @@ -4977,11 +4900,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx": { "no-restricted-imports": { "count": 1 @@ -5085,16 +5003,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx": { "ts/no-explicit-any": { "count": 1 @@ -5110,11 +5018,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx": { "no-restricted-imports": { "count": 1 @@ -5294,11 +5197,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/loop/components/condition-add.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx": { "ts/no-explicit-any": { "count": 1 @@ -5314,11 +5212,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/loop/components/condition-number-input.tsx": { "no-restricted-imports": { "count": 1 @@ -6095,14 +5988,6 @@ "count": 5 } }, - "web/app/education-apply/search-input.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/education-apply/verify-state-modal.tsx": { "react/set-state-in-effect": { "count": 1 diff --git a/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx index 6726ba0583..91fe47d83d 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx @@ -10,6 +10,72 @@ vi.mock('@/next/navigation', () => ({ usePathname: () => '/test', })) +vi.mock('@langgenius/dify-ui/popover', async () => { + const React = await import('react') + const PopoverContext = React.createContext({ + open: false, + setOpen: (_open: boolean) => {}, + }) + + const Popover = ({ + children, + open: controlledOpen, + onOpenChange, + }: { + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false) + const isControlled = controlledOpen !== undefined + const open = isControlled ? !!controlledOpen : uncontrolledOpen + const setOpen = (nextOpen: boolean) => { + if (!isControlled) + setUncontrolledOpen(nextOpen) + onOpenChange?.(nextOpen) + } + + return ( + + {children} + + ) + } + + const PopoverTrigger = ({ render }: { render: React.ReactNode }) => { + const { open, setOpen } = React.useContext(PopoverContext) + return ( +
setOpen(!open)} + > + {render} +
+ ) + } + + const PopoverContent = ({ + children, + ...props + }: React.HTMLAttributes & { children?: React.ReactNode }) => { + const { open } = React.useContext(PopoverContext) + if (!open) + return null + + return ( +
+ {children} +
+ ) + } + + return { + Popover, + PopoverTrigger, + PopoverContent, + } +}) + type PortalToFollowElemProps = { children: React.ReactNode open?: boolean @@ -209,20 +275,17 @@ describe('ContextVar', () => { // Act render() - const triggers = screen.getAllByTestId('portal-trigger') - const varPickerTrigger = triggers[triggers.length - 1] + const varPickerTrigger = screen.getByTestId('popover-trigger') await user.click(varPickerTrigger!) - expect(screen.getByTestId('portal-content'))!.toBeInTheDocument() + expect(screen.getByTestId('popover-content'))!.toBeInTheDocument() // Select a different option - const options = screen.getAllByText('var2') - expect(options.length).toBeGreaterThan(0) - await user.click(options[0]!) + await user.click(screen.getByText('var2')) // Assert expect(onChange).toHaveBeenCalledWith('var2') - expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument() + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() }) it('should toggle dropdown when clicking the trigger button', async () => { @@ -233,16 +296,15 @@ describe('ContextVar', () => { // Act render() - const triggers = screen.getAllByTestId('portal-trigger') - const varPickerTrigger = triggers[triggers.length - 1] + const varPickerTrigger = screen.getByTestId('popover-trigger') // Open dropdown await user.click(varPickerTrigger!) - expect(screen.getByTestId('portal-content'))!.toBeInTheDocument() + expect(screen.getByTestId('popover-content'))!.toBeInTheDocument() // Close dropdown await user.click(varPickerTrigger!) - expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument() + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/app/configuration/dataset-config/context-var/__tests__/var-picker.spec.tsx b/web/app/components/app/configuration/dataset-config/context-var/__tests__/var-picker.spec.tsx index 1d81a31091..7890343720 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/__tests__/var-picker.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/__tests__/var-picker.spec.tsx @@ -18,18 +18,21 @@ type PortalToFollowElemProps = { type PortalToFollowElemTriggerProps = React.HTMLAttributes & { children?: React.ReactNode, asChild?: boolean } type PortalToFollowElemContentProps = React.HTMLAttributes & { children?: React.ReactNode } -vi.mock('@/app/components/base/portal-to-follow-elem', () => { - const PortalContext = React.createContext({ open: false }) +vi.mock('@langgenius/dify-ui/popover', () => { + const PortalContext = React.createContext({ + open: false, + onOpenChange: undefined as ((open: boolean) => void) | undefined, + }) - const PortalToFollowElem = ({ children, open }: PortalToFollowElemProps) => { + const Popover = ({ children, open, onOpenChange }: PortalToFollowElemProps) => { return ( - +
{children}
) } - const PortalToFollowElemContent = ({ children, ...props }: PortalToFollowElemContentProps) => { + const PopoverContent = ({ children, ...props }: PortalToFollowElemContentProps) => { const { open } = React.useContext(PortalContext) if (!open) return null @@ -40,24 +43,41 @@ vi.mock('@/app/components/base/portal-to-follow-elem', () => { ) } - const PortalToFollowElemTrigger = ({ children, asChild, ...props }: PortalToFollowElemTriggerProps) => { + const PopoverTrigger = ({ children, asChild, render, ...props }: PortalToFollowElemTriggerProps & { render?: React.ReactNode }) => { + const { open, onOpenChange } = React.useContext(PortalContext) + const content = render ?? children + const handleClick = (e: React.MouseEvent) => { + props.onClick?.(e) + if (!props.onClick) + onOpenChange?.(!open) + } + + if (React.isValidElement(content)) { + return React.cloneElement(content, { + ...props, + 'onClick': handleClick, + 'data-testid': 'portal-trigger', + } as React.HTMLAttributes) + } + if (asChild && React.isValidElement(children)) { return React.cloneElement(children, { ...props, + 'onClick': handleClick, 'data-testid': 'portal-trigger', } as React.HTMLAttributes) } return ( -
- {children} +
+ {content}
) } return { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, + Popover, + PopoverContent, + PopoverTrigger, } }) diff --git a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx index d29b2e34df..9bac1c7a41 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx @@ -3,15 +3,15 @@ import type { FC } from 'react' import type { IInputTypeIconProps } from '@/app/components/app/configuration/config-var/input-type-icon' import { ChevronDownIcon } from '@heroicons/react/24/outline' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import IconTypeIcon from '@/app/components/app/configuration/config-var/input-type-icon' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' type Option = { name: string, value: string, type: string } export type Props = { @@ -33,6 +33,7 @@ const VarItem: FC<{ item: Option }> = ({ item }) => (
) + const VarPicker: FC = ({ triggerClassName, className, @@ -45,47 +46,51 @@ const VarPicker: FC = ({ const [open, setOpen] = useState(false) const currItem = options.find(item => item.value === value) const notSetVar = !currItem + return ( - - setOpen(v => !v)}> -
-
- {value - ? ( - - ) - : ( -
- {notSelectedVarTip || t('feature.dataSet.queryVariable.choosePlaceholder', { ns: 'appDebug' })} -
- )} + + +
+
+ {currItem + ? ( + + ) + : ( +
+ {notSelectedVarTip || t('feature.dataSet.queryVariable.choosePlaceholder', { ns: 'appDebug' })} +
+ )} +
+ +
- -
-
- + )} + /> + {options.length > 0 ? (
- {options.map(({ name, value, type }, index) => ( + {options.map(({ name, value, type }) => (
{ onChange(value) @@ -103,9 +108,9 @@ const VarPicker: FC = ({
{t('feature.dataSet.queryVariable.noVarTip', { ns: 'appDebug' })}
)} - - - + + ) } + export default React.memo(VarPicker) diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx index 5875b4fb6a..4dc41a307b 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx @@ -2,16 +2,20 @@ import type { FC } from 'react' import { Avatar } from '@langgenius/dify-ui/avatar' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import * as React from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { useMembers } from '@/service/use-common' type Props = { - value?: any - onSelect: (value: any) => void + value?: string + onSelect: (value: string) => void exclude?: string[] } @@ -27,12 +31,9 @@ const MemberSelector: FC = ({ const { data } = useMembers() const currentValue = useMemo(() => { - if (!data?.accounts) + if (!data?.accounts || !value) return null - const accounts = data.accounts || [] - if (!value) - return null - return accounts.find(account => account.id === value) + return data.accounts.find(account => account.id === value) ?? null }, [data, value]) const filteredList = useMemo(() => { @@ -47,37 +48,36 @@ const MemberSelector: FC = ({ return name.toLowerCase().includes(searchValue.toLowerCase()) || email.toLowerCase().includes(searchValue.toLowerCase()) }).filter(account => !exclude.includes(account.id)) - }, [data, searchValue, exclude]) + }, [data, exclude, searchValue]) return ( - - setOpen(v => !v)} + + + {!currentValue && ( +
{t('members.transferModal.transferPlaceholder', { ns: 'common' })}
+ )} + {currentValue && ( + <> + +
{currentValue.name}
+
{currentValue.email}
+ + )} +
+
+ )} + /> + -
- {!currentValue && ( -
{t('members.transferModal.transferPlaceholder', { ns: 'common' })}
- )} - {currentValue && ( - <> - -
{currentValue.name}
-
{currentValue.email}
- - )} -
-
- -
= ({ ))}
-
- + + ) } + export default MemberSelector diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx index 37d828591f..1eb02fb15a 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx @@ -4,6 +4,59 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types' import { SubscriptionSelectorEntry } from '../selector-entry' +vi.mock('@langgenius/dify-ui/popover', async () => { + const React = await import('react') + const PopoverContext = React.createContext({ + open: false, + setOpen: (_open: boolean) => {}, + }) + + const Popover = ({ + children, + open: controlledOpen, + onOpenChange, + }: { + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false) + const isControlled = controlledOpen !== undefined + const open = isControlled ? !!controlledOpen : uncontrolledOpen + const setOpen = (nextOpen: boolean) => { + if (!isControlled) + setUncontrolledOpen(nextOpen) + onOpenChange?.(nextOpen) + } + + return ( + + {children} + + ) + } + + const PopoverTrigger = ({ render }: { render: React.ReactNode }) => { + const { open, setOpen } = React.useContext(PopoverContext) + return ( +
setOpen(!open)}> + {render} +
+ ) + } + + const PopoverContent = ({ children }: { children: React.ReactNode }) => { + const { open } = React.useContext(PopoverContext) + return open ?
{children}
: null + } + + return { + Popover, + PopoverTrigger, + PopoverContent, + } +}) + let mockSubscriptions: TriggerSubscription[] = [] const mockRefetch = vi.fn() @@ -92,6 +145,6 @@ describe('SubscriptionSelectorEntry', () => { fireEvent.click(screen.getByRole('button', { name: 'Subscription One' })) expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ id: 'sub-1', name: 'Subscription One' }), expect.any(Function)) - expect(screen.queryByText('Subscription One')).not.toBeInTheDocument() + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx index 5f755ff634..21f1d4898b 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx @@ -1,28 +1,26 @@ 'use client' import type { SimpleSubscription } from './types' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine, RiWebhookLine } from '@remixicon/react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { SubscriptionList } from '@/app/components/plugins/plugin-detail-panel/subscription-list' import { SubscriptionListMode } from './types' import { useSubscriptionList } from './use-subscription-list' type SubscriptionTriggerButtonProps = { selectedId?: string - onClick?: () => void isOpen?: boolean className?: string } const SubscriptionTriggerButton: React.FC = ({ selectedId, - onClick, isOpen = false, className, }) => { @@ -44,7 +42,7 @@ const SubscriptionTriggerButton: React.FC = ({ } if (subscriptions && subscriptions.length > 0) { - const selectedSubscription = subscriptions?.find(sub => sub.id === selectedId) + const selectedSubscription = subscriptions.find(sub => sub.id === selectedId) if (!selectedSubscription) { return { @@ -67,13 +65,13 @@ const SubscriptionTriggerButton: React.FC = ({ return (
+ )} + /> +
- -
+ + ) } diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx index c87673b750..12fc796531 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx @@ -61,6 +61,9 @@ const setupMocks = (plugins: PluginStatus[] = []) => { return { mockMutateAsync, mockHandleRefetch } } +const getTaskMenuTrigger = () => + document.getElementById('plugin-task-trigger')!.closest('[role="button"]') as HTMLElement + describe('usePluginTaskStatus Hook', () => { beforeEach(() => { vi.clearAllMocks() @@ -637,7 +640,7 @@ describe('PluginTasks Component', () => { render() // Click to open - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) // The popover content should be visible (PluginTaskList) // The popover content should be visible (PluginTaskList) @@ -666,7 +669,7 @@ describe('PluginTasks Component', () => { render() // Open popover - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) // Wait for popover content to render await waitFor(() => { @@ -692,7 +695,7 @@ describe('PluginTasks Component', () => { render() - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) await waitFor(() => { expect(document.querySelector('.w-\\[360px\\]')).toBeInTheDocument() @@ -713,16 +716,14 @@ describe('PluginTasks Component', () => { render() // Open popover - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) await waitFor(() => { expect(document.querySelector('.w-\\[360px\\]'))!.toBeInTheDocument() }) // Find and click the clear all button in error section - const clearButtons = screen.getAllByRole('button') - if (clearButtons.length > 0) - fireEvent.click(clearButtons[0]!) + fireEvent.click(screen.getByRole('button', { name: /task\.clearAll/i })) await waitFor(() => { expect(mockMutateAsync).toHaveBeenCalled() @@ -741,7 +742,7 @@ describe('PluginTasks Component', () => { render() // Open popover - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) await waitFor(() => { expect(document.querySelector('.w-\\[360px\\]'))!.toBeInTheDocument() @@ -813,7 +814,7 @@ describe('PluginTasks Component', () => { render() // Open popover - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) expect(document.querySelector('.w-\\[360px\\]'))!.toBeInTheDocument() }) @@ -825,7 +826,7 @@ describe('PluginTasks Component', () => { ]) render() - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) expect(document.querySelector('.w-\\[360px\\]')).toBeInTheDocument() }) @@ -837,7 +838,7 @@ describe('PluginTasks Component', () => { ]) render() - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) expect(document.querySelector('.w-\\[360px\\]')).toBeInTheDocument() }) @@ -892,7 +893,7 @@ describe('PluginTasks Integration', () => { render() // Open popover - fireEvent.click(document.getElementById('plugin-task-trigger')!) + fireEvent.click(getTaskMenuTrigger()) // All sections should be visible const sections = document.querySelectorAll('.max-h-\\[300px\\]') diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx index 7603cae33d..f3102c4909 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx @@ -97,6 +97,7 @@ const PluginTasks = () => { onOpenChange={setOpen} > } disabled={!canOpenMenu} > diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx index 19ce12b328..ed3c457aaf 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx @@ -61,35 +61,78 @@ vi.mock('@/service/use-plugins', () => ({ }), })) -// Mock portal component for ToolPicker and StrategyPicker +// Mock popover component for ToolPicker and StrategyPicker let mockPortalOpen = false let forcePortalContentVisible = false // Allow tests to force content visibility -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open, onOpenChange: _onOpenChange }: { +let mockPortalOnOpenChange: ((open: boolean) => void) | undefined +vi.mock('@langgenius/dify-ui/popover', () => ({ + Popover: ({ children, open = false, onOpenChange }: { children: React.ReactNode - open: boolean - onOpenChange: (open: boolean) => void + open?: boolean + onOpenChange?: (open: boolean) => void }) => { mockPortalOpen = open + mockPortalOnOpenChange = onOpenChange + return ( +
{children}
+ ) + }, + PopoverTrigger: ({ children, render, onClick, className }: { + children?: React.ReactNode + render?: React.ReactNode + onClick?: (e: React.MouseEvent) => void + className?: string + }) => ( +
{ + onClick?.(e) + if (!onClick) + mockPortalOnOpenChange?.(!mockPortalOpen) + }} + className={className} + > + {render ?? children} +
+ ), + PopoverContent: ({ children, className, popupClassName }: { + children: React.ReactNode + className?: string + popupClassName?: string + }) => { + if (!mockPortalOpen && !forcePortalContentVisible) + return null + return
{children}
+ }, +})) + +vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ + PortalToFollowElem: ({ children, open = false, onOpenChange }: { + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => { + mockPortalOpen = open + mockPortalOnOpenChange = onOpenChange return
{children}
}, PortalToFollowElemTrigger: ({ children, onClick, className }: { - children: React.ReactNode - onClick: (e: React.MouseEvent) => void + children?: React.ReactNode + onClick?: (e: React.MouseEvent) => void className?: string }) => (
{children}
), - PortalToFollowElemContent: ({ children, className }: { + PortalToFollowElemContent: ({ children, className, popupClassName }: { children: React.ReactNode className?: string + popupClassName?: string }) => { - // Allow forcing content visibility for testing option selection if (!mockPortalOpen && !forcePortalContentVisible) return null - return
{children}
+ return
{children}
}, })) @@ -319,6 +362,7 @@ describe('auto-update-setting', () => { beforeEach(() => { vi.clearAllMocks() mockPortalOpen = false + mockPortalOnOpenChange = undefined forcePortalContentVisible = false mockPluginsData.plugins = [] }) diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx index e89b1b3161..176598158c 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-picker.spec.tsx @@ -4,8 +4,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { PluginSource } from '@/app/components/plugins/types' import ToolPicker from '../tool-picker' -let portalOpen = false - const mockInstalledPluginList = vi.hoisted(() => ({ data: { plugins: [] as PluginDetail[], @@ -21,33 +19,51 @@ vi.mock('@/app/components/base/loading', () => ({ default: () =>
loading
, })) -vi.mock('@/app/components/base/portal-to-follow-elem', async () => { - const _React = await import('react') +vi.mock('@langgenius/dify-ui/popover', async () => { + const React = await import('react') + const PopoverContext = React.createContext({ + open: false, + setOpen: (_open: boolean) => {}, + }) + + const Popover = ({ + children, + open, + onOpenChange, + }: { + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => ( + onOpenChange?.(nextOpen) }}> + {children} + + ) + + const PopoverTrigger = ({ render }: { render: React.ReactNode }) => { + const { open, setOpen } = React.useContext(PopoverContext) + return ( +
setOpen(!open)}> + {render} +
+ ) + } + + const PopoverContent = ({ + children, + className, + }: { + children: React.ReactNode + className?: string + }) => { + const { open } = React.useContext(PopoverContext) + return open ?
{children}
: null + } + return { - PortalToFollowElem: ({ - open, - children, - }: { - open: boolean - children: React.ReactNode - }) => { - portalOpen = open - return
{children}
- }, - PortalToFollowElemTrigger: ({ - children, - onClick, - }: { - children: React.ReactNode - onClick: () => void - }) => , - PortalToFollowElemContent: ({ - children, - className, - }: { - children: React.ReactNode - className?: string - }) => portalOpen ?
{children}
: null, + Popover, + PopoverTrigger, + PopoverContent, } }) @@ -118,7 +134,6 @@ const createPlugin = ( describe('ToolPicker', () => { beforeEach(() => { vi.clearAllMocks() - portalOpen = false mockInstalledPluginList.data = { plugins: [], } @@ -137,7 +152,7 @@ describe('ToolPicker', () => { />, ) - fireEvent.click(screen.getByTestId('trigger')) + fireEvent.click(screen.getByText('trigger')) expect(onShowChange).toHaveBeenCalledWith(true) }) diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx index 975dd2217e..1c499104c2 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx @@ -2,15 +2,15 @@ import type { FC } from 'react' import type { ActivePluginType } from '../../marketplace/constants' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import * as React from 'react' -import { useCallback, useMemo, useState } from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import SearchBox from '@/app/components/plugins/marketplace/search-box' import { useInstalledPluginList } from '@/service/use-plugins' import { PLUGIN_TYPE_SEARCH_MAP } from '../../marketplace/constants' @@ -24,7 +24,6 @@ type Props = { onChange: (value: string[]) => void isShow: boolean onShowChange: (isShow: boolean) => void - } const ToolPicker: FC = ({ @@ -35,43 +34,16 @@ const ToolPicker: FC = ({ onShowChange, }) => { const { t } = useTranslation() - const toggleShowPopup = useCallback(() => { - onShowChange(!isShow) - }, [onShowChange, isShow]) const tabs = [ - { - key: PLUGIN_TYPE_SEARCH_MAP.all, - name: t('category.all', { ns: 'plugin' }), - }, - { - key: PLUGIN_TYPE_SEARCH_MAP.model, - name: t('category.models', { ns: 'plugin' }), - }, - { - key: PLUGIN_TYPE_SEARCH_MAP.tool, - name: t('category.tools', { ns: 'plugin' }), - }, - { - key: PLUGIN_TYPE_SEARCH_MAP.agent, - name: t('category.agents', { ns: 'plugin' }), - }, - { - key: PLUGIN_TYPE_SEARCH_MAP.extension, - name: t('category.extensions', { ns: 'plugin' }), - }, - { - key: PLUGIN_TYPE_SEARCH_MAP.datasource, - name: t('category.datasources', { ns: 'plugin' }), - }, - { - key: PLUGIN_TYPE_SEARCH_MAP.trigger, - name: t('category.triggers', { ns: 'plugin' }), - }, - { - key: PLUGIN_TYPE_SEARCH_MAP.bundle, - name: t('category.bundles', { ns: 'plugin' }), - }, + { key: PLUGIN_TYPE_SEARCH_MAP.all, name: t('category.all', { ns: 'plugin' }) }, + { key: PLUGIN_TYPE_SEARCH_MAP.model, name: t('category.models', { ns: 'plugin' }) }, + { key: PLUGIN_TYPE_SEARCH_MAP.tool, name: t('category.tools', { ns: 'plugin' }) }, + { key: PLUGIN_TYPE_SEARCH_MAP.agent, name: t('category.agents', { ns: 'plugin' }) }, + { key: PLUGIN_TYPE_SEARCH_MAP.extension, name: t('category.extensions', { ns: 'plugin' }) }, + { key: PLUGIN_TYPE_SEARCH_MAP.datasource, name: t('category.datasources', { ns: 'plugin' }) }, + { key: PLUGIN_TYPE_SEARCH_MAP.trigger, name: t('category.triggers', { ns: 'plugin' }) }, + { key: PLUGIN_TYPE_SEARCH_MAP.bundle, name: t('category.bundles', { ns: 'plugin' }) }, ] const [pluginType, setPluginType] = useState(PLUGIN_TYPE_SEARCH_MAP.all) @@ -89,14 +61,13 @@ const ToolPicker: FC = ({ ) }) }, [data, pluginType, query, tags]) - const handleCheckChange = useCallback((pluginId: string) => { - return () => { - const newValue = value.includes(pluginId) - ? value.filter(id => id !== pluginId) - : [...value, pluginId] - onChange(newValue) - } - }, [onChange, value]) + + const handleCheckChange = (pluginId: string) => { + const newValue = value.includes(pluginId) + ? value.filter(id => id !== pluginId) + : [...value, pluginId] + onChange(newValue) + } const listContent = (
@@ -105,7 +76,7 @@ const ToolPicker: FC = ({ key={item.plugin_id} payload={item} isChecked={value.includes(item.plugin_id)} - onCheckChange={handleCheckChange(item.plugin_id)} + onCheckChange={() => handleCheckChange(item.plugin_id)} /> ))}
@@ -121,21 +92,18 @@ const ToolPicker: FC = ({ ) + const resolvedTrigger = React.isValidElement(trigger) ? trigger :
{trigger}
+ return ( - - + + - {trigger} - - -
+
= ({
- { - tabs.map(tab => ( -
setPluginType(tab.key)} - > - {tab.name} -
- )) - } + {tabs.map(tab => ( +
setPluginType(tab.key)} + > + {tab.name} +
+ ))}
{!isLoading && filteredList.length > 0 && listContent} {!isLoading && filteredList.length === 0 && noData} {isLoading && loadingContent}
- - + + ) } diff --git a/web/app/components/tools/labels/__tests__/selector.spec.tsx b/web/app/components/tools/labels/__tests__/selector.spec.tsx index b495d2d227..4ef4534759 100644 --- a/web/app/components/tools/labels/__tests__/selector.spec.tsx +++ b/web/app/components/tools/labels/__tests__/selector.spec.tsx @@ -2,6 +2,65 @@ import { act, fireEvent, render, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import LabelSelector from '../selector' +vi.mock('@langgenius/dify-ui/popover', async () => { + const React = await import('react') + const PopoverContext = React.createContext({ + open: false, + setOpen: (_open: boolean) => {}, + }) + + const Popover = ({ + children, + open: controlledOpen, + onOpenChange, + }: { + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false) + const isControlled = controlledOpen !== undefined + const open = isControlled ? !!controlledOpen : uncontrolledOpen + const setOpen = (nextOpen: boolean) => { + if (!isControlled) + setUncontrolledOpen(nextOpen) + onOpenChange?.(nextOpen) + } + + return ( + + {children} + + ) + } + + const PopoverTrigger = ({ render }: { render: React.ReactNode }) => { + const { open, setOpen } = React.useContext(PopoverContext) + return ( +
setOpen(!open)}> + {render} +
+ ) + } + + const PopoverContent = ({ + children, + ...props + }: React.HTMLAttributes & { children?: React.ReactNode }) => { + const { open } = React.useContext(PopoverContext) + if (!open) + return null + + return
{children}
+ } + + return { + Popover, + PopoverTrigger, + PopoverContent, + } +}) + // Mock useTags hook with controlled test data const mockTags = [ { name: 'agent', label: 'Agent' }, diff --git a/web/app/components/tools/labels/selector.tsx b/web/app/components/tools/labels/selector.tsx index 830b4d7aaf..67539715a7 100644 --- a/web/app/components/tools/labels/selector.tsx +++ b/web/app/components/tools/labels/selector.tsx @@ -1,6 +1,11 @@ import type { FC } from 'react' import type { Label } from '@/app/components/tools/labels/constant' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine } from '@remixicon/react' import { useDebounceFn } from 'ahooks' import { noop } from 'es-toolkit/function' @@ -9,17 +14,13 @@ import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import { Tag03 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useTags } from '@/app/components/plugins/hooks' type LabelSelectorProps = { value: string[] onChange: (v: string[]) => void } + const LabelSelector: FC = ({ value, onChange, @@ -34,6 +35,7 @@ const LabelSelector: FC = ({ const { run: handleSearch } = useDebounceFn(() => { setSearchKeywords(keywords) }, { wait: 500 }) + const handleKeywordsChange = (value: string) => { setKeywords(value) handleSearch() @@ -55,32 +57,31 @@ const LabelSelector: FC = ({ } return ( - +
- setOpen(v => !v)} - className="block" - > -
+
0 ? selectedLabels : ''} className={cn('grow truncate text-[13px] leading-[18px] text-text-secondary', !value.length && 'text-text-quaternary!')}> + {!value.length && t('createTool.toolInput.labelPlaceholder', { ns: 'tools' })} + {!!value.length && selectedLabels} +
+
+ +
+
)} - > -
0 ? selectedLabels : ''} className={cn('grow truncate text-[13px] leading-[18px] text-text-secondary', !value.length && 'text-text-quaternary!')}> - {!value.length && t('createTool.toolInput.labelPlaceholder', { ns: 'tools' })} - {!!value.length && selectedLabels} -
-
- -
-
- - + /> +
= ({ )}
-
+
-
+ ) } diff --git a/web/app/components/workflow/__tests__/custom-edge.spec.tsx b/web/app/components/workflow/__tests__/custom-edge.spec.tsx index f8ff9a1a0e..5c98402d9e 100644 --- a/web/app/components/workflow/__tests__/custom-edge.spec.tsx +++ b/web/app/components/workflow/__tests__/custom-edge.spec.tsx @@ -231,5 +231,9 @@ describe('CustomEdge', () => { expect(screen.getByTestId('base-edge')).toHaveAttribute('data-stroke', 'var(--color-workflow-link-line-normal)') expect(screen.getByTestId('block-selector')).toHaveAttribute('data-trigger-class', 'hover:scale-150 transition-all') + expect(screen.getByTestId('block-selector').parentElement).toHaveStyle({ + opacity: '0', + pointerEvents: 'none', + }) }) }) diff --git a/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx b/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx index 9c55d174fd..1ec4ed241f 100644 --- a/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx @@ -455,12 +455,12 @@ describe('ToolPicker', () => { it('should create a custom collection from the add button and refresh custom tools', async () => { const user = userEvent.setup() - const { container } = renderToolPicker({ + renderToolPicker({ isShow: true, supportAddCustomTool: true, }) - const addCustomToolButton = Array.from(container.querySelectorAll('button')).find((button) => { + const addCustomToolButton = Array.from(document.querySelectorAll('button')).find((button) => { return button.className.includes('bg-components-button-primary-bg') }) diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index e7e948a54d..0ee25a9ce6 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -8,17 +8,17 @@ import type { ToolDefaultValue, ToolValue } from './types' import type { CustomCollectionBackend } from '@/app/components/tools/types' import type { BlockEnum, OnSelectBlock } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { toast } from '@langgenius/dify-ui/toast' import { useSuspenseQuery } from '@tanstack/react-query' import { useBoolean } from 'ahooks' import * as React from 'react' -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import SearchBox from '@/app/components/plugins/marketplace/search-box' import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' import AllTools from '@/app/components/workflow/block-selector/all-tools' @@ -43,7 +43,7 @@ type Props = { disabled: boolean trigger: React.ReactNode placement?: Placement - offset?: OffsetOptions + offset?: OffsetOptions | number isShow: boolean onShowChange: (isShow: boolean) => void onSelect: (tool: ToolDefaultValue) => void @@ -120,12 +120,6 @@ const ToolPicker: FC = ({ const handleAddedCustomTool = invalidateCustomTools - const handleTriggerClick = () => { - if (disabled) - return - onShowChange(true) - } - const handleSelect = (_type: BlockEnum, tool?: ToolDefaultValue) => { onSelect(tool!) } @@ -139,6 +133,11 @@ const ToolPicker: FC = ({ setTrue: showEditCustomCollectionModal, }] = useBoolean(false) + const handleShowAddCustomCollectionModal = useCallback(() => { + onShowChange(false) + showEditCustomCollectionModal() + }, [onShowChange, showEditCustomCollectionModal]) + const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => { await createCustomCollection(data) toast.success(t('api.actionSuccess', { ns: 'common' })) @@ -157,20 +156,35 @@ const ToolPicker: FC = ({ ) } - return ( - - - {trigger} - + const resolvedTrigger = React.isValidElement(trigger) ? trigger :
{trigger}
+ const resolvedOffset = typeof offset === 'object' && offset !== null + ? offset as { mainAxis?: number, crossAxis?: number, alignmentAxis?: number | null } + : undefined + const sideOffset = typeof offset === 'number' ? offset : resolvedOffset?.mainAxis ?? 0 + const alignOffset = typeof offset === 'number' ? 0 : resolvedOffset?.crossAxis ?? resolvedOffset?.alignmentAxis ?? 0 - + return ( + { + if (disabled && nextOpen) + return + onShowChange(nextOpen) + }} + > + { + if (disabled) + e.preventDefault() + }} + /> +
= ({ placeholder={t('searchTools', { ns: 'plugin' })!} supportAddCustomTool={supportAddCustomTool} onAddedCustomTool={handleAddedCustomTool} - onShowAddCustomCollectionModal={showEditCustomCollectionModal} + onShowAddCustomCollectionModal={handleShowAddCustomCollectionModal} inputClassName="grow" />
@@ -209,8 +223,8 @@ const ToolPicker: FC = ({ }} />
-
-
+ + ) } diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index af0fb19e6b..d8c3b59a4a 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -63,6 +63,7 @@ const CustomEdge = ({ _sourceRunningStatus, _targetRunningStatus, } = data + const isTriggerVisible = !!(data?._hovering || isTriggerHovered || open) const linearGradientId = useMemo(() => { if ( @@ -144,16 +145,15 @@ const CustomEdge = ({
setIsTriggerHovered(true)} onMouseLeave={() => setIsTriggerHovered(false)} diff --git a/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx b/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx new file mode 100644 index 0000000000..995d3c8a70 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx @@ -0,0 +1,446 @@ +import type { ReactNode } from 'react' +import type { StrategyPluginDetail } from '@/app/components/plugins/types' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { PluginCategoryEnum } from '@/app/components/plugins/types' +import { AgentStrategySelector } from '../agent-strategy-selector' + +const mocks = vi.hoisted(() => ({ + useSuspenseQuery: vi.fn(), + useStrategyProviders: vi.fn(), + useMarketplacePlugins: vi.fn(), + useStrategyInfo: vi.fn(), + refetchStrategyInfo: vi.fn(), + queryPluginsWithDebounced: vi.fn(), +})) + +vi.mock('@tanstack/react-query', () => ({ + useSuspenseQuery: mocks.useSuspenseQuery, +})) + +vi.mock('@/service/system-features', () => ({ + systemFeaturesQueryOptions: () => ({}), +})) + +vi.mock('@/service/use-strategy', () => ({ + useStrategyProviders: mocks.useStrategyProviders, +})) + +vi.mock('@/app/components/plugins/marketplace/hooks', () => ({ + useMarketplacePlugins: mocks.useMarketplacePlugins, +})) + +vi.mock('@/app/components/workflow/nodes/agent/use-config', () => ({ + useStrategyInfo: mocks.useStrategyInfo, +})) + +vi.mock('@/app/components/plugins/install-plugin/base/use-get-icon', () => ({ + default: () => ({ + getIconUrl: (icon: string) => `https://example.com/${icon}`, + }), +})) + +vi.mock('@/app/components/base/search-input', () => ({ + default: ({ + value, + onChange, + placeholder, + }: { + value: string + onChange: (value: string) => void + placeholder?: string + className?: string + }) => ( + onChange(e.target.value)} + /> + ), +})) + +vi.mock('@/app/components/workflow/block-selector/view-type-select', () => ({ + default: ({ + onChange, + }: { + viewType: string + onChange: (value: string) => void + }) => ( + + ), + ViewType: { + flat: 'flat', + grid: 'grid', + }, +})) + +vi.mock('@/app/components/workflow/block-selector/tools', () => ({ + default: ({ + tools, + onSelect, + }: { + tools: Array<{ + id: string + name: string + meta?: unknown + tools: Array<{ + name: string + label: string | { en_US?: string } + output_schema?: Record + }> + }> + onSelect: (value: unknown, tool: { + tool_name: string + provider_name: string + tool_label: string + output_schema?: Record + provider_id: string + meta?: unknown + }) => void + }) => ( +
+ {tools.map(tool => ( +
+ {tool.name} + +
+ ))} +
+ ), +})) + +vi.mock('@/app/components/workflow/block-selector/market-place-plugin/list', () => ({ + default: ({ + list, + searchText, + }: { + list: Array<{ plugin_id: string }> + searchText: string + }) => ( +
+ {`${searchText}:${list.map(item => item.plugin_id).join(',')}`} +
+ ), +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/install-plugin-button', () => ({ + InstallPluginButton: ({ + onClick, + }: { + onClick?: (event: { stopPropagation: () => void }) => void + uniqueIdentifier: string + size: string + }) => ( + + ), +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/switch-plugin-version', () => ({ + SwitchPluginVersion: ({ + onChange, + }: { + onChange: () => void + uniqueIdentifier: string + tooltip: ReactNode + }) => ( + + ), +})) + +vi.mock('@/next/link', () => ({ + default: ({ + href, + children, + className, + }: { + href: string + children: ReactNode + className?: string + }) =>
{children}, +})) + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +vi.mock('@langgenius/dify-ui/popover', async () => { + const React = await import('react') + const PopoverContext = React.createContext({ + open: false, + setOpen: (_open: boolean) => {}, + }) + + const Popover = ({ + children, + open: controlledOpen, + onOpenChange, + }: { + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false) + const isControlled = controlledOpen !== undefined + const open = isControlled ? !!controlledOpen : uncontrolledOpen + const setOpen = (nextOpen: boolean) => { + if (!isControlled) + setUncontrolledOpen(nextOpen) + onOpenChange?.(nextOpen) + } + + return ( + + {children} + + ) + } + + const PopoverTrigger = ({ render }: { render: React.ReactNode }) => { + const { open, setOpen } = React.useContext(PopoverContext) + return ( +
setOpen(!open)}> + {render} +
+ ) + } + + const PopoverContent = ({ children }: { children: React.ReactNode }) => { + const { open } = React.useContext(PopoverContext) + return open ?
{children}
: null + } + + return { + Popover, + PopoverTrigger, + PopoverContent, + } +}) + +vi.mock('@langgenius/dify-ui/tooltip', () => ({ + Tooltip: ({ children }: { children: ReactNode }) =>
{children}
, + TooltipTrigger: ({ render }: { render: ReactNode }) =>
{render}
, + TooltipContent: ({ children }: { children: ReactNode }) =>
{children}
, +})) + +const createStrategyDetail = ( + name: string, + strategyName: string, + strategyLabel: string, +): StrategyPluginDetail => ({ + plugin_unique_identifier: `provider/${name}`, + plugin_id: `plugin-${name}`, + declaration: { + identity: { + author: 'Dify', + name, + description: { en_US: `${name} description` }, + icon: `${name}.png`, + label: { en_US: `${name} label` }, + tags: [], + }, + strategies: [{ + identity: { + name: strategyName, + author: 'Dify', + label: { en_US: strategyLabel }, + }, + description: { en_US: `${strategyLabel} description` }, + parameters: [], + output_schema: { result: { type: 'string' } }, + }], + }, + meta: { version: '1.0.0' }, +} as unknown as StrategyPluginDetail) + +describe('AgentStrategySelector', () => { + const alphaDetail = createStrategyDetail('alpha', 'alpha-strategy', 'Alpha Strategy') + const betaDetail = createStrategyDetail('beta', 'beta-strategy', 'Beta Strategy') + + beforeEach(() => { + vi.clearAllMocks() + + mocks.useSuspenseQuery.mockReturnValue({ data: true }) + mocks.useStrategyProviders.mockReturnValue({ data: [alphaDetail, betaDetail] }) + mocks.useMarketplacePlugins.mockReturnValue({ + queryPluginsWithDebounced: mocks.queryPluginsWithDebounced, + plugins: [{ plugin_id: 'market-agent' }], + }) + mocks.useStrategyInfo.mockReturnValue({ + strategyStatus: undefined, + refetch: mocks.refetchStrategyInfo, + }) + }) + + it('filters strategies and queries marketplace when searching', async () => { + const user = userEvent.setup() + + render( + , + ) + + await user.click(screen.getByTestId('agent-strategy-trigger')) + + expect(screen.getByText('alpha')).toBeInTheDocument() + expect(screen.getByText('beta')).toBeInTheDocument() + expect(screen.getByTestId('plugin-list')).toHaveTextContent(':market-agent') + + await user.type( + screen.getByRole('textbox', { name: 'nodes.agent.strategy.searchPlaceholder' }), + 'alp', + ) + + await waitFor(() => { + expect(mocks.queryPluginsWithDebounced).toHaveBeenLastCalledWith({ + query: 'alp', + category: PluginCategoryEnum.agent, + }) + }) + + expect(screen.getByText('alpha')).toBeInTheDocument() + expect(screen.queryByText('beta')).not.toBeInTheDocument() + }) + + it('maps the selected tool and closes the popover', async () => { + const user = userEvent.setup() + const onChange = vi.fn() + + render( + , + ) + + await user.click(screen.getByTestId('agent-strategy-trigger')) + await user.click(screen.getByRole('button', { name: 'select-alpha' })) + + expect(onChange).toHaveBeenCalledWith({ + agent_strategy_name: 'alpha-strategy', + agent_strategy_provider_name: 'provider/alpha', + agent_strategy_label: 'Alpha Strategy', + agent_output_schema: { result: { type: 'string' } }, + plugin_unique_identifier: 'provider/alpha', + meta: { version: '1.0.0' }, + }) + expect(screen.queryByTestId('agent-strategy-popover')).not.toBeInTheDocument() + }) + + it('renders the plugin-not-installed warning for external strategies', () => { + mocks.useStrategyInfo.mockReturnValue({ + strategyStatus: { + plugin: { + source: 'external', + installed: false, + }, + isExistInPlugin: true, + }, + refetch: mocks.refetchStrategyInfo, + }) + + render( + , + ) + + expect(screen.getByText('nodes.agent.pluginNotInstalled')).toBeInTheDocument() + expect(screen.getByText('nodes.agent.pluginNotInstalledDesc')).toBeInTheDocument() + }) + + it('renders install and switch-version actions for marketplace strategies', async () => { + const user = userEvent.setup() + + mocks.useStrategyInfo.mockReturnValueOnce({ + strategyStatus: { + plugin: { + source: 'marketplace', + installed: false, + }, + isExistInPlugin: false, + }, + refetch: mocks.refetchStrategyInfo, + }) + + const { rerender } = render( + , + ) + + expect(screen.getByTestId('install-plugin-button')).toBeInTheDocument() + + mocks.useStrategyInfo.mockReturnValue({ + strategyStatus: { + plugin: { + source: 'marketplace', + installed: true, + }, + isExistInPlugin: false, + }, + refetch: mocks.refetchStrategyInfo, + }) + + rerender( + , + ) + + await user.click(screen.getByTestId('switch-plugin-version')) + + expect(mocks.refetchStrategyInfo).toHaveBeenCalled() + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index 8e7c1609d1..765c8ea76a 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -4,14 +4,21 @@ import type { Strategy } from './agent-strategy' import type { StrategyPluginDetail } from '@/app/components/plugins/types' import type { ListProps, ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@langgenius/dify-ui/tooltip' import { RiArrowDownSLine, RiErrorWarningFill } from '@remixicon/react' import { useSuspenseQuery } from '@tanstack/react-query' import { memo, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import SearchInput from '@/app/components/base/search-input' -import Tooltip from '@/app/components/base/tooltip' -import { ToolTipContent } from '@/app/components/base/tooltip/content' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' import { PluginCategoryEnum } from '@/app/components/plugins/types' @@ -36,8 +43,11 @@ const NotFoundWarn = (props: { const { t } = useTranslation() return ( - +
} + /> +

{title} @@ -51,11 +61,7 @@ const NotFoundWarn = (props: {

- )} - > -
- -
+
) } @@ -66,18 +72,18 @@ function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => s id: item.plugin_unique_identifier, author: item.declaration.identity.author, name: item.declaration.identity.name, - description: item.declaration.identity.description as any, + description: item.declaration.identity.description as ToolWithProvider['description'], plugin_id: item.plugin_id, icon: getIcon(item.declaration.identity.icon), - label: item.declaration.identity.label as any, + label: item.declaration.identity.label as ToolWithProvider['label'], type: CollectionType.all, meta: item.meta, tools: item.declaration.strategies.map(strategy => ({ name: strategy.identity.name, author: strategy.identity.author, - label: strategy.identity.label as any, + label: strategy.identity.label as ToolWithProvider['tools'][number]['label'], description: strategy.description, - parameters: strategy.parameters as any, + parameters: strategy.parameters as unknown as ToolWithProvider['tools'][number]['parameters'], output_schema: strategy.output_schema, labels: [], })), @@ -151,76 +157,82 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => category: PluginCategoryEnum.agent, }) } - }, [query]) + }, [enable_marketplace, fetchPlugins, query]) const pluginRef = useRef(null) return ( - - -
setOpen(o => !o)} - > - { } - {icon && ( -
- icon -
- )} -

- {value?.agent_strategy_label || t('nodes.agent.strategy.selectTip', { ns: 'workflow' })} -

-
- {showInstallButton && value && ( - e.stopPropagation()} - size="small" - uniqueIdentifier={value.plugin_unique_identifier} - /> + + + {icon && ( +
+ icon +
)} - {showPluginNotInstalledWarn - ? ( - - ) - : showUnsupportedStrategy +

+ {value?.agent_strategy_label || t('nodes.agent.strategy.selectTip', { ns: 'workflow' })} +

+
+ {showInstallButton && value && ( + e.stopPropagation()} + size="small" + uniqueIdentifier={value.plugin_unique_identifier} + /> + )} + {showPluginNotInstalledWarn ? ( ) - : } - {showSwitchVersion && ( - - {t('nodes.agent.strategyNotFoundDescAndSwitchVersion', { ns: 'workflow' })} - - )} - onChange={() => { - refetchStrategyInfo() - }} - /> - )} + : showUnsupportedStrategy + ? ( + + ) + : } + {showSwitchVersion && value && ( + +

+ {t('nodes.agent.unsupportedStrategy', { ns: 'workflow' })} +

+

+ {t('nodes.agent.strategyNotFoundDescAndSwitchVersion', { ns: 'workflow' })} +

+
+ )} + onChange={() => { + refetchStrategyInfo() + }} + /> + )} +
-
- - + )} + /> +
@@ -260,8 +272,8 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => )}
-
- + + ) }) diff --git a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx index c401377450..a01d2a0387 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/__tests__/var-reference-picker.trigger.spec.tsx @@ -1,4 +1,8 @@ import type { ComponentProps } from 'react' +import { + Popover, + PopoverContent, +} from '@langgenius/dify-ui/popover' import { fireEvent, render, screen } from '@testing-library/react' import { BlockEnum, VarType } from '@/app/components/workflow/types' import { VarType as VarKindType } from '../../../../tool/types' @@ -38,46 +42,53 @@ const createProps = ( ], varName: '', variableCategory: 'system', - WrapElem: 'div', - VarPickerWrap: 'div', ...overrides, }) +const renderWithPopover = ( + overrides: Partial> = {}, +) => { + const onOpenChange = vi.fn() + + render( + + + +
picker-content
+
+
, + ) + + return { onOpenChange } +} + describe('VarReferencePickerTrigger', () => { it('should show the placeholder state and open the picker for variable mode', () => { - const setOpen = vi.fn() - render( - , - ) + const { onOpenChange } = renderWithPopover({ + placeholder: 'Pick variable', + }) expect(screen.getByText('Pick variable'))!.toBeInTheDocument() fireEvent.click(screen.getByTestId('var-reference-picker-trigger')) - expect(setOpen).toHaveBeenCalledWith(true) + expect(onOpenChange).toHaveBeenCalledWith(true, expect.anything()) }) it('should render the selected variable state and clear it', () => { const handleClearVar = vi.fn() const handleVariableJump = vi.fn() - render( - , - ) + renderWithPopover({ + handleClearVar, + handleVariableJump, + hasValue: true, + outputVarNode: { title: 'Source Node', desc: '', type: BlockEnum.Code }, + outputVarNodeId: 'node-a', + type: VarType.string, + value: ['node-a', 'answer'], + varName: 'answer', + }) expect(screen.getByText('Source Node'))!.toBeInTheDocument() expect(screen.getByText('answer'))!.toBeInTheDocument() @@ -93,20 +104,16 @@ describe('VarReferencePickerTrigger', () => { const setControlFocus = vi.fn() const setOpen = vi.fn() - render( - , - ) + renderWithPopover({ + isConstant: true, + isSupportConstantValue: true, + schemaWithDynamicSelect: { + type: 'text-input', + } as never, + setOpen, + setControlFocus, + value: 'constant-value', + }) fireEvent.click(screen.getByTestId('var-reference-picker-trigger')) expect(setControlFocus).toHaveBeenCalledTimes(1) @@ -116,38 +123,27 @@ describe('VarReferencePickerTrigger', () => { }) it('should render add button trigger in table mode', () => { - render( - , - ) + renderWithPopover({ + hasValue: true, + isAddBtnTrigger: true, + isInTable: true, + value: ['node-a', 'answer'], + varName: 'answer', + }) - expect(document.querySelector('button'))!.toBeInTheDocument() + expect(screen.getByTestId('add-button'))!.toBeInTheDocument() }) it('should stay inert in readonly mode and show value type placeholder badge', () => { - const setOpen = vi.fn() - - render( - , - ) + const { onOpenChange } = renderWithPopover({ + placeholder: 'Readonly placeholder', + readonly: true, + typePlaceHolder: 'string', + valueTypePlaceHolder: 'text', + }) fireEvent.click(screen.getByTestId('var-reference-picker-trigger')) - expect(setOpen).not.toHaveBeenCalled() + expect(onOpenChange).not.toHaveBeenCalled() expect(screen.getByText('string'))!.toBeInTheDocument() expect(screen.getByText('text'))!.toBeInTheDocument() }) @@ -155,17 +151,13 @@ describe('VarReferencePickerTrigger', () => { it('should show loading placeholder and remove rows in table mode', () => { const onRemove = vi.fn() - render( - , - ) + renderWithPopover({ + hasValue: false, + isInTable: true, + isLoading: true, + onRemove, + placeholder: 'Loading variable', + }) expect(screen.getByText('Loading variable'))!.toBeInTheDocument() diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx index 5a0362a07e..7f77f37987 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.trigger.tsx @@ -7,6 +7,7 @@ import type { Tool } from '@/app/components/tools/types' import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' import type { Node, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' +import { PopoverTrigger } from '@langgenius/dify-ui/popover' import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiArrowDownSLine, RiCloseLine, RiErrorWarningFill, RiLoader4Line, RiMoreLine } from '@remixicon/react' @@ -68,8 +69,6 @@ type Props = { varKindTypes: Array<{ label: string, value: VarKindType }> varName: string variableCategory: string - WrapElem: React.ElementType - VarPickerWrap: React.ElementType } const VarReferencePickerTrigger: FC = ({ @@ -114,9 +113,14 @@ const VarReferencePickerTrigger: FC = ({ varKindTypes, varName, variableCategory, - VarPickerWrap, - WrapElem, }) => { + const handleTriggerReadonlyClick = (e: React.MouseEvent) => { + if (!readonly) + return + e.preventDefault() + e.stopPropagation() + } + const pill = (
{hasValue @@ -212,18 +216,36 @@ const VarReferencePickerTrigger: FC = ({ ) : pill - return ( - { - if (readonly) - return - if (!isConstant) - setOpen(!open) - else - setControlFocus(Date.now()) - }} + const variablePicker = ( +
+
+ {hoveredPill} +
+
+ ) + + const resolvedVariablePicker = isSupportConstantValue + ? ( + readonly + ? variablePicker + : ( + + ) + ) + : variablePicker + + const triggerContent = ( +
{ + if (!isConstant || readonly) + return + setControlFocus(Date.now()) + }} > <> {isAddBtnTrigger @@ -278,24 +300,7 @@ const VarReferencePickerTrigger: FC = ({ isLoading={isLoading} /> ) - : ( - { - if (readonly) - return - if (!isConstant) - setOpen(!open) - else - setControlFocus(Date.now()) - }} - className="h-full grow" - > -
- {hoveredPill} -
- -
- )} + : resolvedVariablePicker} {(hasValue && !readonly && !isInTable && !isJustShowValue) && (
= ({ )} - +
) + + if (!isSupportConstantValue) { + if (readonly) + return triggerContent + + return ( + + ) + } + + return triggerContent } export default VarReferencePickerTrigger diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 9b985c8a9e..7e99988ae8 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -6,6 +6,11 @@ import type { Tool } from '@/app/components/tools/types' import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' import type { CommonNodeType, Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { noop } from 'es-toolkit/function' import { produce } from 'immer' import * as React from 'react' @@ -16,11 +21,6 @@ import { useReactFlow, useStoreApi, } from 'reactflow' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useIsChatMode, @@ -141,10 +141,10 @@ const VarReferencePicker: FC = ({ }) const node = nodes.find(n => n.id === nodeId) - const isInIteration = !!(node?.data as any)?.isInIteration + const isInIteration = !!node?.data.isInIteration const iterationNode = isInIteration ? (nodes.find(n => n.id === node?.parentId) ?? null) : null - const isInLoop = !!(node?.data as any)?.isInLoop + const isInLoop = !!node?.data.isInLoop const loopNode = isInLoop ? (nodes.find(n => n.id === node?.parentId) ?? null) : null const triggerRef = useRef(null) @@ -210,13 +210,11 @@ const VarReferencePicker: FC = ({ }, [onChange]) const inputRef = useRef(null) - const [isFocus, setIsFocus] = useState(false) const [controlFocus, setControlFocus] = useState(0) + const isFocus = controlFocus > 0 useEffect(() => { - if (controlFocus && inputRef.current) { + if (controlFocus && inputRef.current) inputRef.current.focus() - setIsFocus(true) - } }, [controlFocus]) const handleVarReferenceChange = useCallback((value: ValueSelector, varInfo: Var) => { @@ -264,7 +262,7 @@ const VarReferencePicker: FC = ({ }, [availableNodes, reactflow, store]) const type = getCurrentVariableType({ - parentNode: (isInIteration ? iterationNode : loopNode) as any, + parentNode: isInIteration ? iterationNode : loopNode, valueSelector: value as ValueSelector, availableNodes, isChatMode, @@ -289,9 +287,6 @@ const VarReferencePicker: FC = ({ maxVarNameWidth, } = getWidthAllocations(triggerWidth, outputVarNode?.title || '', varName || '', type || '') - const WrapElem = isSupportConstantValue ? 'div' : PortalToFollowElemTrigger - const VarPickerWrap = !isSupportConstantValue ? 'div' : PortalToFollowElemTrigger - const hoverPopup = useMemo(() => { const tooltipType = getTooltipContent(hasValue, isShowAPart, isValidVar) if (tooltipType === 'full-path') { @@ -349,15 +344,23 @@ const VarReferencePicker: FC = ({ ) const triggerPlaceholder = placeholder ?? t('common.setVarValuePlaceholder', { ns: 'workflow' }) + const resolvedTrigger = React.isValidElement(trigger) ? trigger :
{trigger}
return (
- - {!!trigger && setOpen(!open)}>{trigger}} + {!!trigger && ( + { + if (readonly) + e.preventDefault() + }} + /> + )} {!trigger && ( = ({ varKindTypes={varKindTypes} varName={varName} variableCategory={variableCategory} - VarPickerWrap={VarPickerWrap} - WrapElem={WrapElem} /> )} - {!isConstant && ( = ({ preferSchemaType={preferSchemaType} /> )} - - + +
) } diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 648e795dcc..28ad104ed7 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -4,6 +4,11 @@ import type { StructuredOutput } from '../../../llm/types' import type { Field } from '@/app/components/workflow/nodes/llm/types' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { useHover } from 'ahooks' import { noop } from 'es-toolkit/function' import * as React from 'react' @@ -13,11 +18,6 @@ import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' import { VarType } from '@/app/components/workflow/types' @@ -143,7 +143,7 @@ const Item: FC = ({ const open = (isObj || isStructureOutput) && isHovering useEffect(() => { onHovering?.(isHovering) - }, [isHovering]) + }, [isHovering, onHovering]) const handleChosen = (e: React.MouseEvent) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() @@ -167,62 +167,70 @@ const Item: FC = ({ () => getVariableCategory({ isEnv, isChatVar, isLoopVar, isRagVariable }), [isEnv, isChatVar, isLoopVar, isRagVariable], ) + + const itemTrigger = ( +
{ + e.preventDefault() + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + }} + > +
+ {!isFlat && ( + + )} + {isFlat && flatVarIcon} + + {!isEnv && !isChatVar && !isRagVariable && ( +
{varName}
+ )} + {isEnv && ( +
{itemData.variable.replace('env.', '')}
+ )} + {isChatVar && ( +
{itemData.variable.replace('conversation.', '')}
+ )} + {isRagVariable && ( +
{itemData.variable.split('.').slice(-1)[0]}
+ )} +
+
{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}
+ { + (isObj || isStructureOutput) && ( + + ) + } +
+ ) + return ( - - -
{ - e.preventDefault() - e.stopPropagation() - e.nativeEvent.stopImmediatePropagation() - }} - > -
- {!isFlat && ( - - )} - {isFlat && flatVarIcon} - - {!isEnv && !isChatVar && !isRagVariable && ( -
{varName}
- )} - {isEnv && ( -
{itemData.variable.replace('env.', '')}
- )} - {isChatVar && ( -
{itemData.variable.replace('conversation.', '')}
- )} - {isRagVariable && ( -
{itemData.variable.split('.').slice(-1)[0]}
- )} -
-
{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}
- { - (isObj || isStructureOutput) && ( - - ) - } -
-
- + {(isStructureOutput || isObj) && ( = ({ }} /> )} - -
+ + ) } diff --git a/web/app/components/workflow/nodes/code/dependency-picker.tsx b/web/app/components/workflow/nodes/code/dependency-picker.tsx index feed5234a7..dd9e97af6f 100644 --- a/web/app/components/workflow/nodes/code/dependency-picker.tsx +++ b/web/app/components/workflow/nodes/code/dependency-picker.tsx @@ -1,5 +1,10 @@ import type { FC } from 'react' import type { CodeDependency } from './types' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiArrowDownSLine, } from '@remixicon/react' @@ -8,7 +13,6 @@ import * as React from 'react' import { useCallback, useState } from 'react' import { Check } from '@/app/components/base/icons/src/vender/line/general' import Input from '@/app/components/base/input' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' type Props = { value: CodeDependency @@ -32,21 +36,22 @@ const DependencyPicker: FC = ({ }, [onChange]) return ( - - setOpen(!open)} className="grow cursor-pointer"> -
-
{value.name}
- -
-
- + +
+
{value.name}
+ +
+
+ )} + /> +
= ({ ))}
- - + + ) } diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/member-selector.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/member-selector.spec.tsx index 823aa68047..e1d959bd86 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/member-selector.spec.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/__tests__/member-selector.spec.tsx @@ -2,6 +2,59 @@ import type { Member } from '@/models/common' import { fireEvent, render, screen } from '@testing-library/react' import MemberSelector from '../member-selector' +vi.mock('@langgenius/dify-ui/popover', async () => { + const React = await import('react') + const PopoverContext = React.createContext({ + open: false, + setOpen: (_open: boolean) => {}, + }) + + const Popover = ({ + children, + open: controlledOpen, + onOpenChange, + }: { + children: import('react').ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false) + const isControlled = controlledOpen !== undefined + const open = isControlled ? !!controlledOpen : uncontrolledOpen + const setOpen = (nextOpen: boolean) => { + if (!isControlled) + setUncontrolledOpen(nextOpen) + onOpenChange?.(nextOpen) + } + + return ( + + {children} + + ) + } + + const PopoverTrigger = ({ render }: { render: import('react').ReactNode }) => { + const { open, setOpen } = React.useContext(PopoverContext) + return ( +
setOpen(!open)}> + {render} +
+ ) + } + + const PopoverContent = ({ children }: { children: import('react').ReactNode }) => { + const { open } = React.useContext(PopoverContext) + return open ?
{children}
: null + } + + return { + Popover, + PopoverTrigger, + PopoverContent, + } +}) + const mockMemberList = vi.hoisted(() => vi.fn()) vi.mock('../member-list', () => ({ diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-input.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-input.tsx index 1f18ef076d..cb8de634a2 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-input.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-input.tsx @@ -1,14 +1,13 @@ import type { Recipient as RecipientItem } from '../../../types' import type { Member } from '@/models/common' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, +} from '@langgenius/dify-ui/popover' import * as React from 'react' import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import EmailItem from './email-item' import MemberList from './member-list' @@ -58,8 +57,7 @@ const EmailInput = ({ if (disabled) return setIsFocus(true) - const input = inputRef.current?.children[0] as HTMLInputElement - input?.focus() + inputRef.current?.focus() } const handleValueChange = (e: React.ChangeEvent) => { @@ -141,28 +139,29 @@ const EmailInput = ({ /> ))} {!disabled && ( - - - setIsFocus(true)} - onBlur={handleInputBlur} - value={searchKey} - onChange={handleValueChange} - onKeyDown={handleKeyDown} - /> - - + + setIsFocus(true)} + onBlur={handleInputBlur} + value={searchKey} + onChange={handleValueChange} + onKeyDown={handleKeyDown} + /> + - - + + )}
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx index c2df6afb15..f091ec5a30 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx @@ -4,12 +4,16 @@ import type { Recipient } from '@/app/components/workflow/nodes/human-input/type import type { Member } from '@/models/common' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiContactsBookLine, } from '@remixicon/react' -import { useState } from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import MemberList from './member-list' const i18nPrefix = 'nodes.humanInput' @@ -31,39 +35,42 @@ const MemberSelector: FC = ({ const [open, setOpen] = useState(false) const [searchValue, setSearchValue] = useState('') + const handleSelect = useCallback((memberId: string) => { + onSelect(memberId) + setOpen(false) + }, [onSelect]) + return ( - - setOpen(v => !v)} + + + +
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.memberSelector.trigger`, { ns: 'workflow' })}
+ + )} + /> + - -
- - -
+
+ ) } + export default MemberSelector diff --git a/web/app/components/workflow/nodes/if-else/components/condition-add.tsx b/web/app/components/workflow/nodes/if-else/components/condition-add.tsx index 85a45ac5c7..b8f5dab85b 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-add.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-add.tsx @@ -5,17 +5,17 @@ import type { Var, } from '@/app/components/workflow/types' import { Button } from '@langgenius/dify-ui/button' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiAddLine } from '@remixicon/react' import { useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' type ConditionAddProps = { @@ -25,6 +25,7 @@ type ConditionAddProps = { onSelectVariable: HandleAddCondition disabled?: boolean } + const ConditionAdd = ({ className, caseId, @@ -38,29 +39,32 @@ const ConditionAdd = ({ const handleSelectVariable = useCallback((valueSelector: ValueSelector, varItem: Var) => { onSelectVariable(caseId, valueSelector, varItem) setOpen(false) - }, [caseId, onSelectVariable, setOpen]) + }, [caseId, onSelectVariable]) return ( - - setOpen(!open)}> - - - + + + + {t('nodes.ifElse.addCondition', { ns: 'workflow' })} + + )} + onClick={(e) => { + if (disabled) + e.preventDefault() + }} + /> +
-
-
+
+ ) } diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx index 2740471610..e94eca7b38 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx @@ -1,5 +1,9 @@ import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' @@ -23,26 +27,25 @@ const ConditionVarSelector = ({ onChange, }: ConditionVarSelectorProps) => { return ( - - onOpenChange(!open)}> -
- -
-
- + + + + + )} + /> +
-
-
+
+ ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx index ea7870431a..2787aafafd 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx @@ -2,8 +2,11 @@ import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-re import type { MetadataInDoc } from '@/models/datasets' import { Button } from '@langgenius/dify-ui/button' import { - RiAddLine, -} from '@remixicon/react' + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' +import { RiAddLine } from '@remixicon/react' import { useCallback, useMemo, @@ -11,11 +14,6 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import MetadataIcon from './metadata-icon' const AddCondition = ({ @@ -36,25 +34,24 @@ const AddCondition = ({ }, [handleAddCondition]) return ( - - setOpen(!open)}> - - - + + + + {t('nodes.knowledgeRetrieval.metadata.panel.add', { ns: 'workflow' })} + + )} + /> +
- { - filteredMetadataList?.map(metadata => ( -
-
- -
-
handleAddConditionWrapped(metadata)} - > - {metadata.name} -
-
{metadata.type}
+ {filteredMetadataList?.map(metadata => ( +
+
+
- )) - } +
handleAddConditionWrapped(metadata)} + > + {metadata.name} +
+
{metadata.type}
+
+ ))}
- - + + ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx index dd0858942d..d4aedbd8b9 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx @@ -1,12 +1,12 @@ import type { VarType } from '@/app/components/workflow/types' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' type ConditionCommonVariableSelectorProps = { variables?: { name: string, type: string, value: string }[] @@ -31,34 +31,17 @@ const ConditionCommonVariableSelector = ({ }, [onChange]) return ( - - { - if (!variables.length) - return - setOpen(!open) - }} - > -
- { - selected && ( + + + {selected && (
{selected.value}
- ) - } - { - !selected && ( + )} + {!selected && ( <>
@@ -68,27 +51,34 @@ const ConditionCommonVariableSelector = ({ {varType}
- ) - } -
-
- + )} +
+ )} + onClick={(e) => { + if (!variables.length) + e.preventDefault() + }} + /> +
- { - variables.map(v => ( -
handleChange(v.value)} - > - - {v.value} -
- )) - } + {variables.map(v => ( +
handleChange(v.value)} + > + + {v.value} +
+ ))}
-
-
+ + ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx index 15f5ec9377..7ef4ed7388 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx @@ -4,14 +4,14 @@ import type { ValueSelector, Var, } from '@/app/components/workflow/types' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import { VarType } from '@/app/components/workflow/types' @@ -34,35 +34,25 @@ const ConditionVariableSelector = ({ const { t } = useTranslation() const [open, setOpen] = useState(false) - const handleChange = useCallback((valueSelector: ValueSelector, varItem: Var) => { - onChange(valueSelector, varItem) + const handleChange = useCallback((nextValueSelector: ValueSelector, varItem: Var) => { + onChange(nextValueSelector, varItem) setOpen(false) }, [onChange]) return ( - - setOpen(!open)}> -
- { - !!valueSelector.length && ( + + + {!!valueSelector.length && ( - ) - } - { - !valueSelector.length && ( + )} + {!valueSelector.length && ( <>
@@ -72,11 +62,16 @@ const ConditionVariableSelector = ({ {varType}
- ) - } -
-
- + )} + + )} + /> +
-
-
+ + ) } diff --git a/web/app/components/workflow/nodes/loop/components/condition-add.tsx b/web/app/components/workflow/nodes/loop/components/condition-add.tsx index 36a09dd434..a28b066098 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-add.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-add.tsx @@ -5,17 +5,17 @@ import type { Var, } from '@/app/components/workflow/types' import { Button } from '@langgenius/dify-ui/button' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { RiAddLine } from '@remixicon/react' import { useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' type ConditionAddProps = { @@ -24,6 +24,7 @@ type ConditionAddProps = { onSelectVariable: HandleAddCondition disabled?: boolean } + const ConditionAdd = ({ className, variables, @@ -36,29 +37,32 @@ const ConditionAdd = ({ const handleSelectVariable = useCallback((valueSelector: ValueSelector, varItem: Var) => { onSelectVariable(valueSelector, varItem) setOpen(false) - }, [onSelectVariable, setOpen]) + }, [onSelectVariable]) return ( - - setOpen(!open)}> - - - + + + + {t('nodes.ifElse.addCondition', { ns: 'workflow' })} + + )} + onClick={(e) => { + if (disabled) + e.preventDefault() + }} + /> +
-
-
+ + ) } diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx index 7a7957ad71..496ed4087d 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx @@ -1,5 +1,9 @@ import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' @@ -23,26 +27,25 @@ const ConditionVarSelector = ({ onChange, }: ConditionVarSelectorProps) => { return ( - - onOpenChange(!open)}> -
- -
-
- + + + + + )} + /> +
-
-
+ + ) } diff --git a/web/app/education-apply/__tests__/search-input.spec.tsx b/web/app/education-apply/__tests__/search-input.spec.tsx new file mode 100644 index 0000000000..bb3cd8cc84 --- /dev/null +++ b/web/app/education-apply/__tests__/search-input.spec.tsx @@ -0,0 +1,159 @@ +import type { ReactNode } from 'react' +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useState } from 'react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import SearchInput from '../search-input' + +const educationMocks = vi.hoisted(() => ({ + schools: ['Alpha University', 'Beta College'], + setSchools: vi.fn(), + querySchoolsWithDebounced: vi.fn(), + handleUpdateSchools: vi.fn(), + hasNext: false, +})) + +vi.mock('../hooks', () => ({ + useEducation: () => educationMocks, +})) + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +vi.mock('@/app/components/base/input', () => ({ + default: ({ + value, + onChange, + placeholder, + className, + }: { + value?: string + onChange: (event: { target: { value: string } }) => void + placeholder?: string + className?: string + }) => ( + onChange({ target: { value: e.target.value } })} + /> + ), +})) + +vi.mock('@langgenius/dify-ui/popover', async () => { + const React = await import('react') + const PopoverContext = React.createContext({ + open: false, + setOpen: (_open: boolean) => {}, + }) + + const Popover = ({ + children, + open: controlledOpen, + onOpenChange, + }: { + children: ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void + }) => { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false) + const isControlled = controlledOpen !== undefined + const open = isControlled ? !!controlledOpen : uncontrolledOpen + const setOpen = (nextOpen: boolean) => { + if (!isControlled) + setUncontrolledOpen(nextOpen) + onOpenChange?.(nextOpen) + } + + return ( + + {children} + + ) + } + + const PopoverTrigger = ({ render }: { render: ReactNode }) => <>{render} + + const PopoverContent = ({ children }: { children: ReactNode }) => { + const { open } = React.useContext(PopoverContext) + return open ?
{children}
: null + } + + return { + Popover, + PopoverTrigger, + PopoverContent, + } +}) + +const ControlledSearchInput = () => { + const [value, setValue] = useState('') + return +} + +describe('education-apply/search-input', () => { + beforeEach(() => { + vi.clearAllMocks() + educationMocks.schools = ['Alpha University', 'Beta College'] + educationMocks.hasNext = false + }) + + it('opens the popover, queries schools, and closes after selection', async () => { + const user = userEvent.setup() + + render() + + const input = screen.getByPlaceholderText('form.schoolName.placeholder') + await user.type(input, 'A') + + expect(educationMocks.setSchools).toHaveBeenCalledWith([]) + expect(educationMocks.querySchoolsWithDebounced).toHaveBeenLastCalledWith({ + keywords: 'A', + page: 0, + }) + + expect(screen.getByTestId('education-search-popover')).toBeInTheDocument() + expect(screen.getByText('Alpha University')).toBeInTheDocument() + + await user.click(screen.getByText('Beta College')) + + expect(screen.getByDisplayValue('Beta College')).toBeInTheDocument() + expect(screen.queryByTestId('education-search-popover')).not.toBeInTheDocument() + }) + + it('loads the next page when the dropdown is scrolled to the bottom', async () => { + const user = userEvent.setup() + educationMocks.hasNext = true + + render() + + await user.type(screen.getByPlaceholderText('form.schoolName.placeholder'), 'A') + + const scrollContainer = screen.getByText('Alpha University').parentElement as HTMLDivElement + Object.defineProperties(scrollContainer, { + scrollTop: { + value: 60, + configurable: true, + }, + scrollHeight: { + value: 100, + configurable: true, + }, + clientHeight: { + value: 40, + configurable: true, + }, + }) + + fireEvent.scroll(scrollContainer) + + expect(educationMocks.handleUpdateSchools).toHaveBeenCalledWith({ + keywords: 'A', + page: 1, + }) + }) +}) diff --git a/web/app/education-apply/search-input.tsx b/web/app/education-apply/search-input.tsx index 47eca921ee..4f930eb3eb 100644 --- a/web/app/education-apply/search-input.tsx +++ b/web/app/education-apply/search-input.tsx @@ -1,4 +1,9 @@ -import type { ChangeEventHandler } from 'react' +import type { ChangeEventHandler, UIEventHandler } from 'react' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@langgenius/dify-ui/popover' import { useCallback, useRef, @@ -6,17 +11,13 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' import { useEducation } from './hooks' type SearchInputProps = { value?: string onChange: (value: string) => void } + const SearchInput = ({ value, onChange, @@ -48,7 +49,7 @@ const SearchInput = ({ keywords, page, }) - }, [querySchoolsWithDebounced, handleUpdateSchools]) + }, [handleUpdateSchools, querySchoolsWithDebounced]) const handleValueChange: ChangeEventHandler = useCallback((e) => { setOpen(true) @@ -58,10 +59,10 @@ const SearchInput = ({ valueRef.current = inputValue onChange(inputValue) handleSearch(true) - }, [onChange, handleSearch, setSchools]) + }, [handleSearch, onChange, setSchools]) - const handleScroll = useCallback((e: Event) => { - const target = e.target as HTMLDivElement + const handleScroll: UIEventHandler = useCallback((e) => { + const target = e.currentTarget const { scrollTop, scrollHeight, @@ -74,48 +75,45 @@ const SearchInput = ({ }, [handleSearch, hasNext]) return ( - - - - - - { - !!schools.length && value && ( -
- { - schools.map((school, index) => ( -
{ - onChange(school) - setOpen(false) - }} - > - {school} -
- )) - } -
- ) - } -
-
+ + + )} + /> + {!!schools.length && !!value && ( + +
+ {schools.map(school => ( +
{ + onChange(school) + setOpen(false) + }} + > + {school} +
+ ))} +
+
+ )} +
) } From 2e1b11bdb2109bd4c9ba41b02628f5c6de44695d Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Tue, 21 Apr 2026 21:29:39 +0900 Subject: [PATCH 030/253] chore: port 2 api as deprecated (#35261) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: 99 --- .../service_api/dataset/document.py | 324 +++++++++++------- .../service_api/dataset/test_document.py | 26 +- 2 files changed, 217 insertions(+), 133 deletions(-) diff --git a/api/controllers/service_api/dataset/document.py b/api/controllers/service_api/dataset/document.py index 6db047567f..bc28ecb6b7 100644 --- a/api/controllers/service_api/dataset/document.py +++ b/api/controllers/service_api/dataset/document.py @@ -1,4 +1,12 @@ +"""Service API endpoints for dataset document management. + +The canonical Service API paths use hyphenated route segments. Legacy underscore +aliases remain registered for backward compatibility, but they must stay marked +deprecated in generated API docs so clients migrate toward the canonical paths. +""" + import json +from collections.abc import Mapping from contextlib import ExitStack from typing import Self from uuid import UUID @@ -117,12 +125,137 @@ register_schema_models( ) -@service_api_ns.route( - "/datasets//document/create_by_text", - "/datasets//document/create-by-text", -) +def _create_document_by_text(tenant_id: str, dataset_id: UUID) -> tuple[Mapping[str, object], int]: + """Create a document from text for both canonical and legacy routes.""" + payload = DocumentTextCreatePayload.model_validate(service_api_ns.payload or {}) + args = payload.model_dump(exclude_none=True) + + dataset_id_str = str(dataset_id) + tenant_id_str = str(tenant_id) + dataset = db.session.scalar( + select(Dataset).where(Dataset.tenant_id == tenant_id_str, Dataset.id == dataset_id_str).limit(1) + ) + + if not dataset: + raise ValueError("Dataset does not exist.") + + if not dataset.indexing_technique and not args["indexing_technique"]: + raise ValueError("indexing_technique is required.") + + embedding_model_provider = payload.embedding_model_provider + embedding_model = payload.embedding_model + if embedding_model_provider and embedding_model: + DatasetService.check_embedding_model_setting(tenant_id_str, embedding_model_provider, embedding_model) + + retrieval_model = payload.retrieval_model + if ( + retrieval_model + and retrieval_model.reranking_model + and retrieval_model.reranking_model.reranking_provider_name + and retrieval_model.reranking_model.reranking_model_name + ): + DatasetService.check_reranking_model_setting( + tenant_id_str, + retrieval_model.reranking_model.reranking_provider_name, + retrieval_model.reranking_model.reranking_model_name, + ) + + if not current_user: + raise ValueError("current_user is required") + + upload_file = FileService(db.engine).upload_text( + text=payload.text, text_name=payload.name, user_id=current_user.id, tenant_id=tenant_id_str + ) + data_source = { + "type": "upload_file", + "info_list": {"data_source_type": "upload_file", "file_info_list": {"file_ids": [upload_file.id]}}, + } + args["data_source"] = data_source + knowledge_config = KnowledgeConfig.model_validate(args) + DocumentService.document_create_args_validate(knowledge_config) + + if not current_user: + raise ValueError("current_user is required") + + try: + documents, batch = DocumentService.save_document_with_dataset_id( + dataset=dataset, + knowledge_config=knowledge_config, + account=current_user, + dataset_process_rule=dataset.latest_process_rule if "process_rule" not in args else None, + created_from="api", + ) + except ProviderTokenNotInitError as ex: + raise ProviderNotInitializeError(ex.description) + document = documents[0] + + documents_and_batch_fields = {"document": marshal(document, document_fields), "batch": batch} + return documents_and_batch_fields, 200 + + +def _update_document_by_text(tenant_id: str, dataset_id: UUID, document_id: UUID) -> tuple[Mapping[str, object], int]: + """Update a document from text for both canonical and legacy routes.""" + payload = DocumentTextUpdate.model_validate(service_api_ns.payload or {}) + dataset = db.session.scalar( + select(Dataset).where(Dataset.tenant_id == tenant_id, Dataset.id == str(dataset_id)).limit(1) + ) + args = payload.model_dump(exclude_none=True) + if not dataset: + raise ValueError("Dataset does not exist.") + + retrieval_model = payload.retrieval_model + if ( + retrieval_model + and retrieval_model.reranking_model + and retrieval_model.reranking_model.reranking_provider_name + and retrieval_model.reranking_model.reranking_model_name + ): + DatasetService.check_reranking_model_setting( + tenant_id, + retrieval_model.reranking_model.reranking_provider_name, + retrieval_model.reranking_model.reranking_model_name, + ) + + # indexing_technique is already set in dataset since this is an update + args["indexing_technique"] = dataset.indexing_technique + + if args.get("text"): + text = args.get("text") + name = args.get("name") + if not current_user: + raise ValueError("current_user is required") + upload_file = FileService(db.engine).upload_text( + text=str(text), text_name=str(name), user_id=current_user.id, tenant_id=tenant_id + ) + data_source = { + "type": "upload_file", + "info_list": {"data_source_type": "upload_file", "file_info_list": {"file_ids": [upload_file.id]}}, + } + args["data_source"] = data_source + + args["original_document_id"] = str(document_id) + knowledge_config = KnowledgeConfig.model_validate(args) + DocumentService.document_create_args_validate(knowledge_config) + + try: + documents, batch = DocumentService.save_document_with_dataset_id( + dataset=dataset, + knowledge_config=knowledge_config, + account=current_user, + dataset_process_rule=dataset.latest_process_rule if "process_rule" not in args else None, + created_from="api", + ) + except ProviderTokenNotInitError as ex: + raise ProviderNotInitializeError(ex.description) + document = documents[0] + + documents_and_batch_fields = {"document": marshal(document, document_fields), "batch": batch} + return documents_and_batch_fields, 200 + + +@service_api_ns.route("/datasets//document/create-by-text") class DocumentAddByTextApi(DatasetApiResource): - """Resource for documents.""" + """Resource for the canonical text document creation route.""" @service_api_ns.expect(service_api_ns.models[DocumentTextCreatePayload.__name__]) @service_api_ns.doc("create_document_by_text") @@ -138,81 +271,43 @@ class DocumentAddByTextApi(DatasetApiResource): @cloud_edition_billing_resource_check("vector_space", "dataset") @cloud_edition_billing_resource_check("documents", "dataset") @cloud_edition_billing_rate_limit_check("knowledge", "dataset") - def post(self, tenant_id, dataset_id): + def post(self, tenant_id: str, dataset_id: UUID): """Create document by text.""" - payload = DocumentTextCreatePayload.model_validate(service_api_ns.payload or {}) - args = payload.model_dump(exclude_none=True) + return _create_document_by_text(tenant_id=tenant_id, dataset_id=dataset_id) - dataset_id = str(dataset_id) - tenant_id = str(tenant_id) - dataset = db.session.scalar( - select(Dataset).where(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).limit(1) + +@service_api_ns.route("/datasets//document/create_by_text") +class DeprecatedDocumentAddByTextApi(DatasetApiResource): + """Deprecated resource alias for text document creation.""" + + @service_api_ns.expect(service_api_ns.models[DocumentTextCreatePayload.__name__]) + @service_api_ns.doc("create_document_by_text_deprecated") + @service_api_ns.doc(deprecated=True) + @service_api_ns.doc( + description=( + "Deprecated legacy alias for creating a new document by providing text content. " + "Use /datasets/{dataset_id}/document/create-by-text instead." ) - - if not dataset: - raise ValueError("Dataset does not exist.") - - if not dataset.indexing_technique and not args["indexing_technique"]: - raise ValueError("indexing_technique is required.") - - embedding_model_provider = payload.embedding_model_provider - embedding_model = payload.embedding_model - if embedding_model_provider and embedding_model: - DatasetService.check_embedding_model_setting(tenant_id, embedding_model_provider, embedding_model) - - retrieval_model = payload.retrieval_model - if ( - retrieval_model - and retrieval_model.reranking_model - and retrieval_model.reranking_model.reranking_provider_name - and retrieval_model.reranking_model.reranking_model_name - ): - DatasetService.check_reranking_model_setting( - tenant_id, - retrieval_model.reranking_model.reranking_provider_name, - retrieval_model.reranking_model.reranking_model_name, - ) - - if not current_user: - raise ValueError("current_user is required") - - upload_file = FileService(db.engine).upload_text( - text=payload.text, text_name=payload.name, user_id=current_user.id, tenant_id=tenant_id - ) - data_source = { - "type": "upload_file", - "info_list": {"data_source_type": "upload_file", "file_info_list": {"file_ids": [upload_file.id]}}, + ) + @service_api_ns.doc(params={"dataset_id": "Dataset ID"}) + @service_api_ns.doc( + responses={ + 200: "Document created successfully", + 401: "Unauthorized - invalid API token", + 400: "Bad request - invalid parameters", } - args["data_source"] = data_source - knowledge_config = KnowledgeConfig.model_validate(args) - # validate args - DocumentService.document_create_args_validate(knowledge_config) - - if not current_user: - raise ValueError("current_user is required") - - try: - documents, batch = DocumentService.save_document_with_dataset_id( - dataset=dataset, - knowledge_config=knowledge_config, - account=current_user, - dataset_process_rule=dataset.latest_process_rule if "process_rule" not in args else None, - created_from="api", - ) - except ProviderTokenNotInitError as ex: - raise ProviderNotInitializeError(ex.description) - document = documents[0] - - documents_and_batch_fields = {"document": marshal(document, document_fields), "batch": batch} - return documents_and_batch_fields, 200 + ) + @cloud_edition_billing_resource_check("vector_space", "dataset") + @cloud_edition_billing_resource_check("documents", "dataset") + @cloud_edition_billing_rate_limit_check("knowledge", "dataset") + def post(self, tenant_id: str, dataset_id: UUID): + """Create document by text through the deprecated underscore alias.""" + return _create_document_by_text(tenant_id=tenant_id, dataset_id=dataset_id) -@service_api_ns.route( - "/datasets//documents//update_by_text", - "/datasets//documents//update-by-text", -) +@service_api_ns.route("/datasets//documents//update-by-text") class DocumentUpdateByTextApi(DatasetApiResource): - """Resource for update documents.""" + """Resource for the canonical text document update route.""" @service_api_ns.expect(service_api_ns.models[DocumentTextUpdate.__name__]) @service_api_ns.doc("update_document_by_text") @@ -229,62 +324,35 @@ class DocumentUpdateByTextApi(DatasetApiResource): @cloud_edition_billing_rate_limit_check("knowledge", "dataset") def post(self, tenant_id: str, dataset_id: UUID, document_id: UUID): """Update document by text.""" - payload = DocumentTextUpdate.model_validate(service_api_ns.payload or {}) - dataset = db.session.scalar( - select(Dataset).where(Dataset.tenant_id == tenant_id, Dataset.id == str(dataset_id)).limit(1) + return _update_document_by_text(tenant_id=tenant_id, dataset_id=dataset_id, document_id=document_id) + + +@service_api_ns.route("/datasets//documents//update_by_text") +class DeprecatedDocumentUpdateByTextApi(DatasetApiResource): + """Deprecated resource alias for text document updates.""" + + @service_api_ns.expect(service_api_ns.models[DocumentTextUpdate.__name__]) + @service_api_ns.doc("update_document_by_text_deprecated") + @service_api_ns.doc(deprecated=True) + @service_api_ns.doc( + description=( + "Deprecated legacy alias for updating an existing document by providing text content. " + "Use /datasets/{dataset_id}/documents/{document_id}/update-by-text instead." ) - args = payload.model_dump(exclude_none=True) - if not dataset: - raise ValueError("Dataset does not exist.") - - retrieval_model = payload.retrieval_model - if ( - retrieval_model - and retrieval_model.reranking_model - and retrieval_model.reranking_model.reranking_provider_name - and retrieval_model.reranking_model.reranking_model_name - ): - DatasetService.check_reranking_model_setting( - tenant_id, - retrieval_model.reranking_model.reranking_provider_name, - retrieval_model.reranking_model.reranking_model_name, - ) - - # indexing_technique is already set in dataset since this is an update - args["indexing_technique"] = dataset.indexing_technique - - if args.get("text"): - text = args.get("text") - name = args.get("name") - if not current_user: - raise ValueError("current_user is required") - upload_file = FileService(db.engine).upload_text( - text=str(text), text_name=str(name), user_id=current_user.id, tenant_id=tenant_id - ) - data_source = { - "type": "upload_file", - "info_list": {"data_source_type": "upload_file", "file_info_list": {"file_ids": [upload_file.id]}}, - } - args["data_source"] = data_source - # validate args - args["original_document_id"] = str(document_id) - knowledge_config = KnowledgeConfig.model_validate(args) - DocumentService.document_create_args_validate(knowledge_config) - - try: - documents, batch = DocumentService.save_document_with_dataset_id( - dataset=dataset, - knowledge_config=knowledge_config, - account=current_user, - dataset_process_rule=dataset.latest_process_rule if "process_rule" not in args else None, - created_from="api", - ) - except ProviderTokenNotInitError as ex: - raise ProviderNotInitializeError(ex.description) - document = documents[0] - - documents_and_batch_fields = {"document": marshal(document, document_fields), "batch": batch} - return documents_and_batch_fields, 200 + ) + @service_api_ns.doc(params={"dataset_id": "Dataset ID", "document_id": "Document ID"}) + @service_api_ns.doc( + responses={ + 200: "Document updated successfully", + 401: "Unauthorized - invalid API token", + 404: "Document not found", + } + ) + @cloud_edition_billing_resource_check("vector_space", "dataset") + @cloud_edition_billing_rate_limit_check("knowledge", "dataset") + def post(self, tenant_id: str, dataset_id: UUID, document_id: UUID): + """Update document by text through the deprecated underscore alias.""" + return _update_document_by_text(tenant_id=tenant_id, dataset_id=dataset_id, document_id=document_id) @service_api_ns.route( diff --git a/api/tests/unit_tests/controllers/service_api/dataset/test_document.py b/api/tests/unit_tests/controllers/service_api/dataset/test_document.py index 12d5e7345d..288659b192 100644 --- a/api/tests/unit_tests/controllers/service_api/dataset/test_document.py +++ b/api/tests/unit_tests/controllers/service_api/dataset/test_document.py @@ -22,6 +22,8 @@ import pytest from werkzeug.exceptions import Forbidden, NotFound from controllers.service_api.dataset.document import ( + DeprecatedDocumentAddByTextApi, + DeprecatedDocumentUpdateByTextApi, DocumentAddByFileApi, DocumentAddByTextApi, DocumentApi, @@ -1005,7 +1007,7 @@ class TestDocumentAddByTextApi: # Act with app.test_request_context( - f"/datasets/{mock_dataset.id}/document/create_by_text", + f"/datasets/{mock_dataset.id}/document/create-by-text", method="POST", json={ "name": "Test Document", @@ -1037,7 +1039,7 @@ class TestDocumentAddByTextApi: # Act & Assert with app.test_request_context( - f"/datasets/{mock_dataset.id}/document/create_by_text", + f"/datasets/{mock_dataset.id}/document/create-by-text", method="POST", json={"name": "Test Document", "text": "Content"}, headers={"Authorization": "Bearer test_token"}, @@ -1066,7 +1068,7 @@ class TestDocumentAddByTextApi: # Act & Assert with app.test_request_context( - f"/datasets/{mock_dataset.id}/document/create_by_text", + f"/datasets/{mock_dataset.id}/document/create-by-text", method="POST", json={"name": "Test Document", "text": "Content"}, headers={"Authorization": "Bearer test_token"}, @@ -1093,6 +1095,20 @@ class TestArchivedDocumentImmutableError: assert error.code == 403 +class TestDocumentTextRouteDeprecation: + """Test that legacy underscore text routes stay marked deprecated.""" + + def test_create_by_text_legacy_alias_is_deprecated(self): + """Ensure only the legacy create-by-text alias is marked deprecated.""" + assert DeprecatedDocumentAddByTextApi.post.__apidoc__["deprecated"] is True + assert DocumentAddByTextApi.post.__apidoc__.get("deprecated") is not True + + def test_update_by_text_legacy_alias_is_deprecated(self): + """Ensure only the legacy update-by-text alias is marked deprecated.""" + assert DeprecatedDocumentUpdateByTextApi.post.__apidoc__["deprecated"] is True + assert DocumentUpdateByTextApi.post.__apidoc__.get("deprecated") is not True + + # ============================================================================= # Endpoint tests for DocumentUpdateByTextApi, DocumentAddByFileApi, # DocumentUpdateByFileApi. @@ -1162,7 +1178,7 @@ class TestDocumentUpdateByTextApiPost: doc_id = str(uuid.uuid4()) with app.test_request_context( - f"/datasets/{mock_dataset.id}/documents/{doc_id}/update_by_text", + f"/datasets/{mock_dataset.id}/documents/{doc_id}/update-by-text", method="POST", json={"name": "Updated Doc", "text": "New content"}, headers={"Authorization": "Bearer test_token"}, @@ -1195,7 +1211,7 @@ class TestDocumentUpdateByTextApiPost: doc_id = str(uuid.uuid4()) with app.test_request_context( - f"/datasets/{mock_dataset.id}/documents/{doc_id}/update_by_text", + f"/datasets/{mock_dataset.id}/documents/{doc_id}/update-by-text", method="POST", json={"name": "Doc", "text": "Content"}, headers={"Authorization": "Bearer test_token"}, From 33eebe8cfc9b6801ff4926bedec79feb9576733e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:17:12 +0900 Subject: [PATCH 031/253] chore(deps): bump lxml from 6.0.2 to 6.1.0 in /api (#35470) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index bab24a87d2..c008dd1de9 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -3687,28 +3687,28 @@ wheels = [ [[package]] name = "lxml" -version = "6.0.2" +version = "6.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, - { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, + { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, + { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, + { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, ] [[package]] From 712e52222035317283fc2fe0a2221bb21db0c165 Mon Sep 17 00:00:00 2001 From: Zakir Jiwani Date: Tue, 21 Apr 2026 20:27:50 -0400 Subject: [PATCH 032/253] fix: bump pyrefly version (#33702) Co-authored-by: Asuka Minato Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/pyproject.toml | 2 +- api/uv.lock | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index fbd5d394ad..31a6ea115c 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -173,7 +173,7 @@ dev = [ # "locust>=2.40.4", # Temporarily removed due to compatibility issues. Uncomment when resolved. "pytest-timeout>=2.4.0", "pytest-xdist>=3.8.0", - "pyrefly>=0.61.1", + "pyrefly>=0.62.0", "xinference-client>=2.5.0", ] diff --git a/api/uv.lock b/api/uv.lock index c008dd1de9..7d6777fa06 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1627,7 +1627,7 @@ dev = [ { name = "lxml-stubs", specifier = ">=0.5.1" }, { name = "mypy", specifier = ">=1.20.1" }, { name = "pandas-stubs", specifier = ">=3.0.0" }, - { name = "pyrefly", specifier = ">=0.61.1" }, + { name = "pyrefly", specifier = ">=0.62.0" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-benchmark", specifier = ">=5.2.3" }, { name = "pytest-cov", specifier = ">=7.1.0" }, @@ -5357,19 +5357,19 @@ wheels = [ [[package]] name = "pyrefly" -version = "0.61.1" +version = "0.62.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/c8/52fce3f0e3718d9ff71d16af41cef925e58613741328004d3aa3fe585057/pyrefly-0.61.1.tar.gz", hash = "sha256:2a871320b7d2b28b8635064b620097d7091e84c49e4808d915ad31dad685d0f5", size = 5535788, upload-time = "2026-04-17T18:47:33.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/8874ed25781e7dd561c6d75fb4a7becf10a18d75b074f25b845cc334f781/pyrefly-0.62.0.tar.gz", hash = "sha256:da1fbe1075dc1e6c8e3134e9370b0a0e7a296061d782cca5bf83dbb8e4c10d7c", size = 5537672, upload-time = "2026-04-20T17:12:15.718Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/38/e94ff401405a05fbf81c9bbfa993a34ffd03be84812b545063c8efb56b44/pyrefly-0.61.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6e3ed857b99291fc4aa3b54ce22deb086c0174cf3a3775eccea7439efd16d925", size = 12969301, upload-time = "2026-04-17T18:47:06.036Z" }, - { url = "https://files.pythonhosted.org/packages/f3/be/53c7f9400696e46633c8cee8b6fd32ce7ab4a965ddf9ac4f4ea9e2034647/pyrefly-0.61.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cf6335c1baf9470ca8113f7ea8bdbd0b96081c82a911157c576cdfc8a67a9a87", size = 12475413, upload-time = "2026-04-17T18:47:08.863Z" }, - { url = "https://files.pythonhosted.org/packages/77/68/83cc3267620b14f81fa596a84efc7ebcf5c49f79b521499e85d1a4fca6d8/pyrefly-0.61.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:844b5baddc2a631f69648a4756c54c97d86e4b9c07e335b216668e24390b77b6", size = 36074785, upload-time = "2026-04-17T18:47:11.845Z" }, - { url = "https://files.pythonhosted.org/packages/d8/00/e8d437995b8dcea022f5310bc873f5de1dcc71da4876d5be917ee9a93fef/pyrefly-0.61.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eaa294f90622c5b3743af8e9de4263447f22bb0e8b60c80cf83292adb4f2d14b", size = 38802979, upload-time = "2026-04-17T18:47:16.058Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/f1cbc58e8875608ae740d9575de95c8bc6d4dce202f82b4fe90005727618/pyrefly-0.61.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a8d8c3fe08b9593dce23ad4bc7c393891a379c2d580aa1f263182567721bd6f", size = 37029339, upload-time = "2026-04-17T18:47:19.601Z" }, - { url = "https://files.pythonhosted.org/packages/18/8c/0ff67041c88c28f48b10ce15758831d1e4e60f11db5bfc09dcffd5edb6ba/pyrefly-0.61.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:305f2086f4d7d796244b337884d96cf0d32435420336a77840ca369cf6fa06fd", size = 41595667, upload-time = "2026-04-17T18:47:23.122Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9e/62b8139b140931593a6b29334802ea6b86d033c0bfd9794950279732253b/pyrefly-0.61.1-py3-none-win32.whl", hash = "sha256:3271a019885a72c8dd064e928bb445af807771506842f5f2faaac17d8e6e73a5", size = 11963660, upload-time = "2026-04-17T18:47:25.86Z" }, - { url = "https://files.pythonhosted.org/packages/38/6e/73280243d12bec28f55b6edd4e70c5cf11e3d7de2395ecb4eb36cca7dab4/pyrefly-0.61.1-py3-none-win_amd64.whl", hash = "sha256:3e3763d5d76f505c5b8897db1446bde8e138d50a67751f2aa76d6c6034254836", size = 12804056, upload-time = "2026-04-17T18:47:28.674Z" }, - { url = "https://files.pythonhosted.org/packages/87/32/38ac5af84d96167412024abf5e2f49f15b777987a1942e7a442e8e5fef82/pyrefly-0.61.1-py3-none-win_arm64.whl", hash = "sha256:cef5631e2ab09702274ec2eaaafee28a114891cf85f2d31568b329727e3ff735", size = 12302467, upload-time = "2026-04-17T18:47:31.409Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ea/09bd9da7d5df294db800312fb415be2fefbaa5594178e9e49f44fa071aea/pyrefly-0.62.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9d78ec4f126dee1fa76215b193b964490ce10e62a32d2787a72c51623658b803", size = 13020414, upload-time = "2026-04-20T17:11:43.617Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f0/f84afac4f220c4c8c801b779ee2ff28ad3f7731f4283c2e1b6ee9012e8c2/pyrefly-0.62.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2a41a34902d20756264486f9e309f22633d100261bd960feea6e858a098d985d", size = 12515659, upload-time = "2026-04-20T17:11:46.59Z" }, + { url = "https://files.pythonhosted.org/packages/40/0b/620c39cefa9ae1b25ee7a2da9d8d3c278b095649cb8435c5e01ea64f7c17/pyrefly-0.62.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4666c6b65aea662e5f77b64dc91c091b7ea5cede6aa66c0f4cbae26480403583", size = 36228332, upload-time = "2026-04-20T17:11:50.523Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fb/47b8b76438c12761e509a3666cd5a99d4af7f21976ba8385feb475cbfe30/pyrefly-0.62.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1aefab798f47d37c13ded791192fee9b39a6d2b12e31f38ae06a1f80c4b26e22", size = 38995741, upload-time = "2026-04-20T17:11:54.702Z" }, + { url = "https://files.pythonhosted.org/packages/55/d2/03bd17673f61147cd5609cd7d6a1455eeccc17a07a7e141ed9931b0c42c0/pyrefly-0.62.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa986b50d56740da1d7ae7c660a505143cb9d286fa98cc7e5f4a759cc6eaa5d", size = 37205321, upload-time = "2026-04-20T17:11:58.9Z" }, + { url = "https://files.pythonhosted.org/packages/75/14/20ba7b7f2d182f9b7c1e24a3041dac9b5730ae28cfe1614a2c98706650f2/pyrefly-0.62.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32e9b175805c82ffb967e4708f4910bace7e1a12736907380cc9afdbaabb0efb", size = 41786834, upload-time = "2026-04-20T17:12:03.221Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c8/5a7ba88c4fa1b5090d877f70fa1b742b921b9e7d8d3f4b6b9b1ba1820850/pyrefly-0.62.0-py3-none-win32.whl", hash = "sha256:1cd98edc20cab5bac8016c9220ee66080e39bd22e7f0e9bb3e2c4e2be1555eed", size = 12010170, upload-time = "2026-04-20T17:12:06.791Z" }, + { url = "https://files.pythonhosted.org/packages/2e/78/d8f810de010ff2ed594c630c724fd817ef430963249e9eb396ce8f785e9d/pyrefly-0.62.0-py3-none-win_amd64.whl", hash = "sha256:6994f8ee7d6720325ee52207fbdaca98a799a1efe462bb5ba90c47160f7f3e6e", size = 12861816, upload-time = "2026-04-20T17:12:09.689Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a9/ac824ef6a3f50b7c0ec5974471f8f2cb205cd1edd53a5abbcf7ba37feb5d/pyrefly-0.62.0-py3-none-win_arm64.whl", hash = "sha256:362a5d47a5ac5aaa5258091e878a1759ff8b687d8cf462af1c516144f7b0108a", size = 12352977, upload-time = "2026-04-20T17:12:12.736Z" }, ] [[package]] From ba924fc97beb335e96a3e83656941f87ac78ed8c Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:45:54 +0800 Subject: [PATCH 033/253] chore: migrate type-check from tsc to tsgo across all workspaces (#35488) --- .agents/skills/component-refactoring/SKILL.md | 2 +- .../frontend-testing/references/checklist.md | 2 +- AGENTS.md | 2 +- e2e/package.json | 3 +- packages/dify-ui/README.md | 2 +- packages/dify-ui/package.json | 3 +- packages/dify-ui/tsconfig.json | 4 +- .../package.json | 3 +- .../src/no-unchecked-indexed-access/run.ts | 10 +- pnpm-lock.yaml | 2247 +++++++++-------- pnpm-workspace.yaml | 66 +- sdks/nodejs-client/package.json | 3 +- web/docs/lint.md | 6 +- web/package.json | 3 +- web/scripts/refactor-component.js | 2 +- 15 files changed, 1302 insertions(+), 1056 deletions(-) diff --git a/.agents/skills/component-refactoring/SKILL.md b/.agents/skills/component-refactoring/SKILL.md index 0ed18d71d1..98a94592ab 100644 --- a/.agents/skills/component-refactoring/SKILL.md +++ b/.agents/skills/component-refactoring/SKILL.md @@ -367,7 +367,7 @@ For each extraction: ┌────────────────────────────────────────┐ │ 1. Extract code │ │ 2. Run: pnpm lint:fix │ - │ 3. Run: pnpm type-check:tsgo │ + │ 3. Run: pnpm type-check │ │ 4. Run: pnpm test │ │ 5. Test functionality manually │ │ 6. PASS? → Next extraction │ diff --git a/.agents/skills/frontend-testing/references/checklist.md b/.agents/skills/frontend-testing/references/checklist.md index 99258498dd..519c3f166f 100644 --- a/.agents/skills/frontend-testing/references/checklist.md +++ b/.agents/skills/frontend-testing/references/checklist.md @@ -127,7 +127,7 @@ For the current file being tested: - [ ] Run full directory test: `pnpm test path/to/directory/` - [ ] Check coverage report: `pnpm test:coverage` - [ ] Run `pnpm lint:fix` on all test files -- [ ] Run `pnpm type-check:tsgo` +- [ ] Run `pnpm type-check` ## Common Issues to Watch diff --git a/AGENTS.md b/AGENTS.md index d25d2eed96..8be2daef95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,7 +30,7 @@ The codebase is split into: ## Language Style - **Python**: Keep type hints on functions and attributes, and implement relevant special methods (e.g., `__repr__`, `__str__`). Prefer `TypedDict` over `dict` or `Mapping` for type safety and better code documentation. -- **TypeScript**: Use the strict config, rely on ESLint (`pnpm lint:fix` preferred) plus `pnpm type-check:tsgo`, and avoid `any` types. +- **TypeScript**: Use the strict config, rely on ESLint (`pnpm lint:fix` preferred) plus `pnpm type-check`, and avoid `any` types. ## General Practices diff --git a/e2e/package.json b/e2e/package.json index 94fc857c0b..77d7db80f0 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -12,13 +12,14 @@ "e2e:middleware:down": "tsx ./scripts/setup.ts middleware-down", "e2e:middleware:up": "tsx ./scripts/setup.ts middleware-up", "e2e:reset": "tsx ./scripts/setup.ts reset", - "type-check": "tsc" + "type-check": "tsgo" }, "devDependencies": { "@cucumber/cucumber": "catalog:", "@dify/tsconfig": "workspace:*", "@playwright/test": "catalog:", "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", "tsx": "catalog:", "typescript": "catalog:", "vite": "catalog:", diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md index 5e4e439e5f..e9c762073d 100644 --- a/packages/dify-ui/README.md +++ b/packages/dify-ui/README.md @@ -88,7 +88,7 @@ See `[web/docs/overlay-migration.md](../../web/docs/overlay-migration.md)` for t - `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives. - `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`. -- `pnpm -C packages/dify-ui type-check` — `tsc --noEmit` for this package only. +- `pnpm -C packages/dify-ui type-check` — `tsgo --noEmit` for this package only. See `[AGENTS.md](./AGENTS.md)` for: diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json index 408ba2c432..483db46986 100644 --- a/packages/dify-ui/package.json +++ b/packages/dify-ui/package.json @@ -83,7 +83,7 @@ "storybook:build": "storybook build", "test": "vp test", "test:watch": "vp test --watch", - "type-check": "tsc" + "type-check": "tsgo" }, "peerDependencies": { "@base-ui/react": "catalog:", @@ -109,6 +109,7 @@ "@tailwindcss/vite": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", + "@typescript/native-preview": "catalog:", "@vitejs/plugin-react": "catalog:", "@vitest/coverage-v8": "catalog:", "class-variance-authority": "catalog:", diff --git a/packages/dify-ui/tsconfig.json b/packages/dify-ui/tsconfig.json index 10cffbcd76..514954c807 100644 --- a/packages/dify-ui/tsconfig.json +++ b/packages/dify-ui/tsconfig.json @@ -2,5 +2,7 @@ "extends": "@dify/tsconfig/react.json", "compilerOptions": { "types": ["vite-plus/test/globals"] - } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "tailwind.config.ts"], + "exclude": ["node_modules", "dist", "storybook-static", "coverage"] } diff --git a/packages/migrate-no-unchecked-indexed-access/package.json b/packages/migrate-no-unchecked-indexed-access/package.json index 5da8d4cb50..6a29f40338 100644 --- a/packages/migrate-no-unchecked-indexed-access/package.json +++ b/packages/migrate-no-unchecked-indexed-access/package.json @@ -8,9 +8,10 @@ }, "scripts": { "build": "vp pack", - "type-check": "tsc" + "type-check": "tsgo" }, "dependencies": { + "@typescript/native-preview": "catalog:", "typescript": "catalog:" }, "devDependencies": { diff --git a/packages/migrate-no-unchecked-indexed-access/src/no-unchecked-indexed-access/run.ts b/packages/migrate-no-unchecked-indexed-access/src/no-unchecked-indexed-access/run.ts index ad655e4f11..6eea6c2459 100644 --- a/packages/migrate-no-unchecked-indexed-access/src/no-unchecked-indexed-access/run.ts +++ b/packages/migrate-no-unchecked-indexed-access/src/no-unchecked-indexed-access/run.ts @@ -117,17 +117,17 @@ async function runTypeCheck( await fs.mkdir(TYPECHECK_CACHE_DIR, { recursive: true }) - const tscArgs = ['exec', 'tsc', '--noEmit', '--pretty', 'false'] + const tsgoArgs = ['exec', 'tsgo', '--noEmit', '--pretty', 'false'] if (incremental) { - tscArgs.push('--incremental', '--tsBuildInfoFile', buildInfoPath) + tsgoArgs.push('--incremental', '--tsBuildInfoFile', buildInfoPath) } else { - tscArgs.push('--incremental', 'false') + tsgoArgs.push('--incremental', 'false') } - tscArgs.push('--project', projectPath) + tsgoArgs.push('--project', projectPath) try { - const { stdout, stderr } = await execFileAsync('pnpm', tscArgs, { + const { stdout, stderr } = await execFileAsync('pnpm', tsgoArgs, { cwd: projectDirectory, env: { ...process.env, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d573664e3a..9408bfb4b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,11 +7,11 @@ settings: catalogs: default: '@amplitude/analytics-browser': - specifier: 2.39.0 - version: 2.39.0 + specifier: 2.41.0 + version: 2.41.0 '@amplitude/plugin-session-replay-browser': - specifier: 1.27.7 - version: 1.27.7 + specifier: 1.27.10 + version: 1.27.10 '@antfu/eslint-config': specifier: 8.2.0 version: 8.2.0 @@ -22,8 +22,8 @@ catalogs: specifier: 5.1.2 version: 5.1.2 '@cucumber/cucumber': - specifier: 12.8.0 - version: 12.8.0 + specifier: 12.8.1 + version: 12.8.1 '@egoist/tailwindcss-icons': specifier: 1.9.2 version: 1.9.2 @@ -88,11 +88,11 @@ catalogs: specifier: 4.7.0 version: 4.7.0 '@next/eslint-plugin-next': - specifier: 16.2.3 - version: 16.2.3 + specifier: 16.2.4 + version: 16.2.4 '@next/mdx': - specifier: 16.2.3 - version: 16.2.3 + specifier: 16.2.4 + version: 16.2.4 '@orpc/client': specifier: 1.13.14 version: 1.13.14 @@ -115,8 +115,8 @@ catalogs: specifier: 4.2.0 version: 4.2.0 '@sentry/react': - specifier: 10.48.0 - version: 10.48.0 + specifier: 10.49.0 + version: 10.49.0 '@storybook/addon-docs': specifier: 10.3.5 version: 10.3.5 @@ -148,35 +148,35 @@ catalogs: specifier: 0.13.11 version: 0.13.11 '@tailwindcss/postcss': - specifier: 4.2.2 - version: 4.2.2 + specifier: 4.2.4 + version: 4.2.4 '@tailwindcss/typography': specifier: 0.5.19 version: 0.5.19 '@tailwindcss/vite': - specifier: 4.2.2 - version: 4.2.2 + specifier: 4.2.4 + version: 4.2.4 '@tanstack/eslint-plugin-query': - specifier: 5.99.0 - version: 5.99.0 + specifier: 5.99.2 + version: 5.99.2 '@tanstack/react-devtools': specifier: 0.10.2 version: 0.10.2 '@tanstack/react-form': - specifier: 1.29.0 - version: 1.29.0 + specifier: 1.29.1 + version: 1.29.1 '@tanstack/react-form-devtools': - specifier: 0.2.21 - version: 0.2.21 + specifier: 0.2.22 + version: 0.2.22 '@tanstack/react-query': - specifier: 5.99.0 - version: 5.99.0 + specifier: 5.99.2 + version: 5.99.2 '@tanstack/react-query-devtools': - specifier: 5.99.0 - version: 5.99.0 + specifier: 5.99.2 + version: 5.99.2 '@tanstack/react-virtual': - specifier: 3.13.23 - version: 3.13.23 + specifier: 3.13.24 + version: 3.13.24 '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -190,14 +190,14 @@ catalogs: specifier: 14.6.1 version: 14.6.1 '@tsslint/cli': - specifier: 3.0.3 - version: 3.0.3 + specifier: 3.0.4 + version: 3.0.4 '@tsslint/compat-eslint': - specifier: 3.0.3 - version: 3.0.3 + specifier: 3.0.4 + version: 3.0.4 '@tsslint/config': - specifier: 3.0.3 - version: 3.0.3 + specifier: 3.0.4 + version: 3.0.4 '@types/js-cookie': specifier: 3.0.6 version: 3.0.6 @@ -223,14 +223,14 @@ catalogs: specifier: 1.15.9 version: 1.15.9 '@typescript-eslint/eslint-plugin': - specifier: 8.58.2 - version: 8.58.2 + specifier: 8.59.0 + version: 8.59.0 '@typescript-eslint/parser': - specifier: 8.58.2 - version: 8.58.2 + specifier: 8.59.0 + version: 8.59.0 '@typescript/native-preview': - specifier: 7.0.0-dev.20260413.1 - version: 7.0.0-dev.20260413.1 + specifier: 7.0.0-dev.20260422.1 + version: 7.0.0-dev.20260422.1 '@vitejs/plugin-react': specifier: 6.0.1 version: 6.0.1 @@ -238,8 +238,8 @@ catalogs: specifier: 0.5.24 version: 0.5.24 '@vitest/coverage-v8': - specifier: 4.1.4 - version: 4.1.4 + specifier: 4.1.5 + version: 4.1.5 abcjs: specifier: 6.6.2 version: 6.6.2 @@ -277,8 +277,8 @@ catalogs: specifier: 10.6.0 version: 10.6.0 dompurify: - specifier: 3.4.0 - version: 3.4.0 + specifier: 3.4.1 + version: 3.4.1 echarts: specifier: 6.0.0 version: 6.0.0 @@ -298,11 +298,11 @@ catalogs: specifier: 5.6.0 version: 5.6.0 es-toolkit: - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.46.0 + version: 1.46.0 eslint: - specifier: 10.2.0 - version: 10.2.0 + specifier: 10.2.1 + version: 10.2.1 eslint-markdown: specifier: 0.6.1 version: 0.6.1 @@ -322,8 +322,8 @@ catalogs: specifier: 0.5.2 version: 0.5.2 eslint-plugin-sonarjs: - specifier: 4.0.2 - version: 4.0.2 + specifier: 4.0.3 + version: 4.0.3 eslint-plugin-storybook: specifier: 10.3.5 version: 10.3.5 @@ -346,8 +346,8 @@ catalogs: specifier: 1.11.13 version: 1.11.13 i18next: - specifier: 26.0.4 - version: 26.0.4 + specifier: 26.0.6 + version: 26.0.6 i18next-resources-to-backend: specifier: 1.2.1 version: 1.2.1 @@ -376,11 +376,11 @@ catalogs: specifier: 0.16.45 version: 0.16.45 knip: - specifier: 6.4.1 - version: 6.4.1 + specifier: 6.6.1 + version: 6.6.1 ky: - specifier: 2.0.0 - version: 2.0.0 + specifier: 2.0.2 + version: 2.0.2 lamejs: specifier: 1.2.1 version: 1.2.1 @@ -388,8 +388,8 @@ catalogs: specifier: 0.43.0 version: 0.43.0 loro-crdt: - specifier: 1.10.8 - version: 1.10.8 + specifier: 1.11.1 + version: 1.11.1 mermaid: specifier: 11.14.0 version: 11.14.0 @@ -403,8 +403,8 @@ catalogs: specifier: 1.0.0 version: 1.0.0 next: - specifier: 16.2.3 - version: 16.2.3 + specifier: 16.2.4 + version: 16.2.4 next-themes: specifier: 0.4.6 version: 0.4.6 @@ -418,8 +418,8 @@ catalogs: specifier: 1.59.1 version: 1.59.1 postcss: - specifier: 8.5.9 - version: 8.5.9 + specifier: 8.5.10 + version: 8.5.10 qrcode.react: specifier: 4.2.0 version: 4.2.0 @@ -502,8 +502,8 @@ catalogs: specifier: 3.5.0 version: 3.5.0 tailwindcss: - specifier: 4.2.2 - version: 4.2.2 + specifier: 4.2.4 + version: 4.2.4 tldts: specifier: 7.0.28 version: 7.0.28 @@ -511,8 +511,8 @@ catalogs: specifier: 4.21.0 version: 4.21.0 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 6.0.3 + version: 6.0.3 uglify-js: specifier: 3.19.3 version: 3.19.3 @@ -585,31 +585,31 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 'catalog:' - version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@next/eslint-plugin-next@16.2.4)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3))(@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) eslint: specifier: 'catalog:' - version: 10.2.0(jiti@2.6.1) + version: 10.2.1(jiti@2.6.1) eslint-markdown: specifier: 'catalog:' - version: 0.6.1(eslint@10.2.0(jiti@2.6.1)) + version: 0.6.1(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-markdown-preferences: specifier: 'catalog:' - version: 0.41.1(@eslint/markdown@8.0.1)(eslint@10.2.0(jiti@2.6.1)) + version: 0.41.1(@eslint/markdown@8.0.1)(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-no-barrel-files: specifier: 'catalog:' - version: 1.3.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 1.3.1(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 - version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) e2e: devDependencies: '@cucumber/cucumber': specifier: 'catalog:' - version: 12.8.0 + version: 12.8.1 '@dify/tsconfig': specifier: workspace:* version: link:../packages/tsconfig @@ -619,18 +619,21 @@ importers: '@types/node': specifier: 'catalog:' version: 25.6.0 + '@typescript/native-preview': + specifier: 'catalog:' + version: 7.0.0-dev.20260422.1 tsx: specifier: 'catalog:' version: 4.21.0 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 - version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) packages/dify-ui: dependencies: @@ -652,13 +655,13 @@ importers: version: link:../tsconfig '@egoist/tailwindcss-icons': specifier: 'catalog:' - version: 1.9.2(tailwindcss@4.2.2) + version: 1.9.2(tailwindcss@4.2.4) '@iconify-json/ri': specifier: 'catalog:' version: 1.2.10 '@storybook/addon-docs': specifier: 'catalog:' - version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-links': specifier: 'catalog:' version: 10.3.5(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) @@ -667,22 +670,25 @@ importers: version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/react-vite': specifier: 'catalog:' - version: 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + version: 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.2(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 4.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)) '@types/react': specifier: 'catalog:' version: 19.2.14 '@types/react-dom': specifier: 'catalog:' version: 19.2.3(@types/react@19.2.14) + '@typescript/native-preview': + specifier: 'catalog:' + version: 7.0.0-dev.20260422.1 '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) class-variance-authority: specifier: 'catalog:' version: 0.7.1 @@ -700,19 +706,19 @@ importers: version: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tailwindcss: specifier: 'catalog:' - version: 4.2.2 + version: 4.2.4 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 - version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) vitest-browser-react: specifier: 'catalog:' - version: 2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) packages/iconify-collections: devDependencies: @@ -725,9 +731,12 @@ importers: packages/migrate-no-unchecked-indexed-access: dependencies: + '@typescript/native-preview': + specifier: 'catalog:' + version: 7.0.0-dev.20260422.1 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 devDependencies: '@dify/tsconfig': specifier: workspace:* @@ -737,10 +746,10 @@ importers: version: 25.6.0 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 - version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) packages/tsconfig: {} @@ -751,43 +760,46 @@ importers: version: link:../../packages/tsconfig '@eslint/js': specifier: 'catalog:' - version: 10.0.1(eslint@10.2.0(jiti@2.6.1)) + version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) '@types/node': specifier: 'catalog:' version: 25.6.0 '@typescript-eslint/eslint-plugin': specifier: 'catalog:' - version: 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/parser': specifier: 'catalog:' - version: 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript/native-preview': + specifier: 'catalog:' + version: 7.0.0-dev.20260422.1 '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) eslint: specifier: 'catalog:' - version: 10.2.0(jiti@2.6.1) + version: 10.2.1(jiti@2.6.1) typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 - version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) vitest: specifier: npm:@voidzero-dev/vite-plus-test@0.1.19 - version: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' web: dependencies: '@amplitude/analytics-browser': specifier: 'catalog:' - version: 2.39.0 + version: 2.41.0 '@amplitude/plugin-session-replay-browser': specifier: 'catalog:' - version: 1.27.7(@amplitude/rrweb@2.0.0-alpha.37) + version: 1.27.10(@amplitude/rrweb@2.0.0-alpha.37) '@base-ui/react': specifier: 'catalog:' version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -841,13 +853,13 @@ importers: version: 1.13.14 '@orpc/tanstack-query': specifier: 'catalog:' - version: 1.13.14(@orpc/client@1.13.14)(@tanstack/query-core@5.99.0) + version: 1.13.14(@orpc/client@1.13.14)(@tanstack/query-core@5.99.2) '@remixicon/react': specifier: 'catalog:' version: 4.9.0(react@19.2.5) '@sentry/react': specifier: 'catalog:' - version: 10.48.0(react@19.2.5) + version: 10.49.0(react@19.2.5) '@streamdown/math': specifier: 'catalog:' version: 1.0.2(react@19.2.5) @@ -856,19 +868,19 @@ importers: version: 3.2.5 '@t3-oss/env-nextjs': specifier: 'catalog:' - version: 0.13.11(typescript@6.0.2)(valibot@1.3.1(typescript@6.0.2))(zod@4.3.6) + version: 0.13.11(typescript@6.0.3)(valibot@1.3.1(typescript@6.0.3))(zod@4.3.6) '@tailwindcss/typography': specifier: 'catalog:' - version: 0.5.19(tailwindcss@4.2.2) + version: 0.5.19(tailwindcss@4.2.4) '@tanstack/react-form': specifier: 'catalog:' - version: 1.29.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.29.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-query': specifier: 'catalog:' - version: 5.99.0(react@19.2.5) + version: 5.99.2(react@19.2.5) '@tanstack/react-virtual': specifier: 'catalog:' - version: 3.13.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.13.24(react-dom@19.2.5(react@19.2.5))(react@19.2.5) abcjs: specifier: 'catalog:' version: 6.6.2 @@ -898,7 +910,7 @@ importers: version: 10.6.0 dompurify: specifier: 'catalog:' - version: 3.4.0 + version: 3.4.1 echarts: specifier: 'catalog:' version: 6.0.0 @@ -919,7 +931,7 @@ importers: version: 5.6.0 es-toolkit: specifier: 'catalog:' - version: 1.45.1 + version: 1.46.0 fast-deep-equal: specifier: 'catalog:' version: 3.1.3 @@ -934,7 +946,7 @@ importers: version: 1.11.13 i18next: specifier: 'catalog:' - version: 26.0.4(typescript@6.0.2) + version: 26.0.6(typescript@6.0.3) i18next-resources-to-backend: specifier: 'catalog:' version: 1.2.1 @@ -961,7 +973,7 @@ importers: version: 0.16.45 ky: specifier: 'catalog:' - version: 2.0.0 + version: 2.0.2 lamejs: specifier: 'catalog:' version: 1.2.1 @@ -970,7 +982,7 @@ importers: version: 0.43.0 loro-crdt: specifier: 'catalog:' - version: 1.10.8 + version: 1.11.1 mermaid: specifier: 'catalog:' version: 11.14.0 @@ -985,13 +997,13 @@ importers: version: 1.0.0 next: specifier: 'catalog:' - version: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) next-themes: specifier: 'catalog:' version: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) nuqs: specifier: 'catalog:' - version: 2.8.9(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + version: 2.8.9(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) pinyin-pro: specifier: 'catalog:' version: 3.28.1 @@ -1018,7 +1030,7 @@ importers: version: 5.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react-i18next: specifier: 'catalog:' - version: 16.5.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + version: 16.5.8(i18next@26.0.6(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3) react-multi-email: specifier: 'catalog:' version: 1.0.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -1091,7 +1103,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 'catalog:' - version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@next/eslint-plugin-next@16.2.4)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3))(@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) '@chromatic-com/storybook': specifier: 'catalog:' version: 5.1.2(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) @@ -1103,10 +1115,10 @@ importers: version: link:../packages/tsconfig '@egoist/tailwindcss-icons': specifier: 'catalog:' - version: 1.9.2(tailwindcss@4.2.2) + version: 1.9.2(tailwindcss@4.2.4) '@eslint-react/eslint-plugin': specifier: 'catalog:' - version: 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@hono/node-server': specifier: 'catalog:' version: 1.19.14(hono@4.12.14) @@ -1130,16 +1142,16 @@ importers: version: 3.1.1 '@next/eslint-plugin-next': specifier: 'catalog:' - version: 16.2.3 + version: 16.2.4 '@next/mdx': specifier: 'catalog:' - version: 16.2.3(@mdx-js/loader@3.1.1)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5)) + version: 16.2.4(@mdx-js/loader@3.1.1)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5)) '@rgrove/parse-xml': specifier: 'catalog:' version: 4.2.0 '@storybook/addon-docs': specifier: 'catalog:' - version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + version: 10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-links': specifier: 'catalog:' version: 10.3.5(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) @@ -1151,28 +1163,28 @@ importers: version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/nextjs-vite': specifier: 'catalog:' - version: 10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + version: 10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) '@storybook/react': specifier: 'catalog:' - version: 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + version: 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) '@tailwindcss/postcss': specifier: 'catalog:' - version: 4.2.2 + version: 4.2.4 '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.2(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 4.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)) '@tanstack/eslint-plugin-query': specifier: 'catalog:' - version: 5.99.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 5.99.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@tanstack/react-devtools': specifier: 'catalog:' version: 0.10.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-form-devtools': specifier: 'catalog:' - version: 0.2.21(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11) + version: 0.2.22(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11) '@tanstack/react-query-devtools': specifier: 'catalog:' - version: 5.99.0(@tanstack/react-query@5.99.0(react@19.2.5))(react@19.2.5) + version: 5.99.2(@tanstack/react-query@5.99.2(react@19.2.5))(react@19.2.5) '@testing-library/dom': specifier: 'catalog:' version: 10.4.1 @@ -1187,13 +1199,13 @@ importers: version: 14.6.1(@testing-library/dom@10.4.1) '@tsslint/cli': specifier: 'catalog:' - version: 3.0.3(@tsslint/compat-eslint@3.0.3(jiti@2.6.1)(typescript@6.0.2))(typescript@6.0.2) + version: 3.0.4(@tsslint/compat-eslint@3.0.4(jiti@2.6.1)(typescript@6.0.3))(typescript@6.0.3) '@tsslint/compat-eslint': specifier: 'catalog:' - version: 3.0.3(jiti@2.6.1)(typescript@6.0.2) + version: 3.0.4(jiti@2.6.1)(typescript@6.0.3) '@tsslint/config': specifier: 'catalog:' - version: 3.0.3(@tsslint/compat-eslint@3.0.3(jiti@2.6.1)(typescript@6.0.2))(typescript@6.0.2) + version: 3.0.4(@tsslint/compat-eslint@3.0.4(jiti@2.6.1)(typescript@6.0.3))(typescript@6.0.3) '@types/js-cookie': specifier: 'catalog:' version: 3.0.6 @@ -1220,19 +1232,19 @@ importers: version: 1.15.9 '@typescript-eslint/parser': specifier: 'catalog:' - version: 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260413.1 + version: 7.0.0-dev.20260422.1 '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + version: 0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) agentation: specifier: 'catalog:' version: 3.0.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -1241,31 +1253,31 @@ importers: version: 1.5.1 eslint: specifier: 'catalog:' - version: 10.2.0(jiti@2.6.1) + version: 10.2.1(jiti@2.6.1) eslint-markdown: specifier: 'catalog:' - version: 0.6.1(eslint@10.2.0(jiti@2.6.1)) + version: 0.6.1(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-better-tailwindcss: specifier: 'catalog:' - version: 4.4.1(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tailwindcss@4.2.2)(typescript@6.0.2) + version: 4.4.1(eslint@10.2.1(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tailwindcss@4.2.4)(typescript@6.0.3) eslint-plugin-hyoban: specifier: 'catalog:' - version: 0.14.1(eslint@10.2.0(jiti@2.6.1)) + version: 0.14.1(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-markdown-preferences: specifier: 'catalog:' - version: 0.41.1(@eslint/markdown@8.0.1)(eslint@10.2.0(jiti@2.6.1)) + version: 0.41.1(@eslint/markdown@8.0.1)(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-no-barrel-files: specifier: 'catalog:' - version: 1.3.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 1.3.1(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) eslint-plugin-react-refresh: specifier: 'catalog:' - version: 0.5.2(eslint@10.2.0(jiti@2.6.1)) + version: 0.5.2(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-sonarjs: specifier: 'catalog:' - version: 4.0.2(eslint@10.2.0(jiti@2.6.1)) + version: 4.0.3(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-storybook: specifier: 'catalog:' - version: 10.3.5(eslint@10.2.0(jiti@2.6.1))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + version: 10.3.5(eslint@10.2.1(jiti@2.6.1))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) happy-dom: specifier: 'catalog:' version: 20.9.0 @@ -1274,10 +1286,10 @@ importers: version: 4.12.14 knip: specifier: 'catalog:' - version: 6.4.1(@emnapi/runtime@1.9.1) + version: 6.6.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) postcss: specifier: 'catalog:' - version: 8.5.9 + version: 8.5.10 react-server-dom-webpack: specifier: 'catalog:' version: 19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -1286,34 +1298,34 @@ importers: version: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tailwindcss: specifier: 'catalog:' - version: 4.2.2 + version: 4.2.4 tsx: specifier: 'catalog:' version: 4.21.0 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 uglify-js: specifier: 'catalog:' version: 3.19.3 vinext: specifier: 'catalog:' - version: 0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + version: 0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.3) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.19 - version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vite-plugin-inspect: specifier: 'catalog:' - version: 12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0) + version: 12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3)(ws@8.20.0) vite-plus: specifier: 'catalog:' - version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + version: 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) vitest: specifier: npm:@voidzero-dev/vite-plus-test@0.1.19 - version: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + version: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vitest-canvas-mock: specifier: 'catalog:' - version: 1.1.4(@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + version: 1.1.4(@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)) packages: @@ -1324,17 +1336,17 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@amplitude/analytics-browser@2.39.0': - resolution: {integrity: sha512-sTNGGjiubsDs1NqKsTXp0ykCaSIzjaGclMRHlnO7JBatqK0f/Knl0cfn1a7XBFuTVix/M5nrWATsKv6+0dSpMg==} + '@amplitude/analytics-browser@2.41.0': + resolution: {integrity: sha512-zCfsm4mvytJRCvXxc04vfI0gmDkVUsfFXwoPl6l3g6uo9xC6Z22heDWot4NLUpeqKbQGBWJLYSzaD08HigXZNA==} - '@amplitude/analytics-client-common@2.4.43': - resolution: {integrity: sha512-R5n3cfnVNLk32BE2DbCp4xpn39mfmjMUjvOO9kt5dLFdF0cozb9MCawVyZJQVfnJJT6k5NMoswdUBu7Ul0nbRw==} + '@amplitude/analytics-client-common@2.4.45': + resolution: {integrity: sha512-2lQRpLEiZp3hqFXSpGgzsOVeXCaDwW8hCKJZeXWB6GGcLsGn0ssEC7RNxLpUMNWCctCF7Dfr9a4MSVe54jtiPw==} '@amplitude/analytics-connector@1.6.4': resolution: {integrity: sha512-SpIv0IQMNIq6SH3UqFGiaZyGSc7PBZwRdq7lvP0pBxW8i4Ny+8zwI0pV+VMfMHQwWY3wdIbWw5WQphNjpdq1/Q==} - '@amplitude/analytics-core@2.45.0': - resolution: {integrity: sha512-vWRYbXu2Grs1GM+WHo03RPtbaPs5sJm21YQcAow9JASvtoY4xNqItIeRydCJQWtFHhbbxY41n+CVW6mzDP6aBA==} + '@amplitude/analytics-core@2.47.0': + resolution: {integrity: sha512-LLffKoq7nhEtFtXz/QGcimlcS3vYugEW14JdAeZE03k2empShrAhCzigHL3Xiz+ywW9KC3inUalnbxybVhU0YA==} '@amplitude/analytics-types@2.11.1': resolution: {integrity: sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A==} @@ -1342,26 +1354,29 @@ packages: '@amplitude/experiment-core@0.7.2': resolution: {integrity: sha512-Wc2NWvgQ+bLJLeF0A9wBSPIaw0XuqqgkPKsoNFQrmS7r5Djd56um75In05tqmVntPJZRvGKU46pAp8o5tdf4mA==} - '@amplitude/plugin-autocapture-browser@1.25.2': - resolution: {integrity: sha512-AWzIX0uit60Q742rH/96/n88e+3BaVZa4+7Xs+BeuuIOyrljOZlQKzH23Lxzkl0DgbNb5+MMqWds0pov3DV5TA==} + '@amplitude/plugin-autocapture-browser@1.26.0': + resolution: {integrity: sha512-LCLsMr8usQJK6R6VjCjmiJ3ZRICh0QJ6xbDEwAm5XhuLFGRNsB2b9eRHlvalsPrTXR+b4Hjr71/dh3XNYZ9rqw==} - '@amplitude/plugin-custom-enrichment-browser@0.1.4': - resolution: {integrity: sha512-vxuQocn8YGE2wMLZUmotRG8c6RijoaQAsHKDQEO56CNk3WhSecgSGMnlHcUcOYIzwfXKFj4MxRJS386kdDHV+Q==} + '@amplitude/plugin-custom-enrichment-browser@0.1.6': + resolution: {integrity: sha512-oAVR5biFh7kMm4XOji7r684TA/VOwK8N1OLMdACQdwBl8MPiBLJDIPWtkVW5iSXyIjwYkOlrjygtnkei1q2S8g==} - '@amplitude/plugin-network-capture-browser@1.9.13': - resolution: {integrity: sha512-8uzTQFbP+dvqJX+S39KqKw+EheJW8JCWT/xlXT55vtTU/ZTFeF074QnHFEKUPewpYXpwKXgJky8PDoMk0b46Qw==} + '@amplitude/plugin-event-property-attribution-browser@0.1.1': + resolution: {integrity: sha512-2YHF/O+WVX0VxTAh3Jh77Ib+LeUl1xbyF1qW2YzGurY8uBUeAd62+7qFaXQSBWk1qMiTguxjKXrbbtxssfWWWg==} - '@amplitude/plugin-page-url-enrichment-browser@0.7.5': - resolution: {integrity: sha512-0Q7P5vsue/s92i3zevVDVJf9AiHkbxGdwkB8iV2oWgkXtglzWugwr//qN+muHmXdi1ZWxRjm93CW+jQJVripgw==} + '@amplitude/plugin-network-capture-browser@1.9.15': + resolution: {integrity: sha512-PkFWjKyOkkzw/9yKKJ2sa19F2Uo9NiSAR0l0NmELcO8h4TVJdfc4HlvM68AnWJ15nkFHh+UoG7SHwb7vp7ZC3Q==} - '@amplitude/plugin-page-view-tracking-browser@2.9.6': - resolution: {integrity: sha512-/4lG2lXIB6qbQNf1VYQ5fDOnvInPEtYuOgvmyLfuZ6PvHVFUu4NZtoOVdAcy0R9x76rNyCpRXxdL78p9Ra1ANA==} + '@amplitude/plugin-page-url-enrichment-browser@0.7.7': + resolution: {integrity: sha512-P67Xmi5/oDFZOO2DfsAvvDS280WdzVsl6JTPvgJc4+WJ1YypbYFA7S87LUIiwtuvgnHXFsgOjNUI36bOEVTW4w==} - '@amplitude/plugin-session-replay-browser@1.27.7': - resolution: {integrity: sha512-KcGMFaBGqZAOm1Gdzio9d95IL3Nmp5J1xOu1PD0NAPYLfW1MyoyA5PFIIlMqqVf1DoCjmgqP7AY4swetU2tpWg==} + '@amplitude/plugin-page-view-tracking-browser@2.10.1': + resolution: {integrity: sha512-XEk0Z7ZfN6gV0h1R2hOZkby/SUTIbGU8SgWR8gt4O+DEx+pxfTQEuCM2ya1YaCV2h1SBrTK4bnIHgPax/4/HoA==} - '@amplitude/plugin-web-vitals-browser@1.1.28': - resolution: {integrity: sha512-gs4Y1eOuVUEDwYEJF82f/GmgQ7iM4Y/eZTkftJKjFsBNbrPro2CuLymfdAcC+QuVfyrp3qAiWcSGnjDXA6ZbQg==} + '@amplitude/plugin-session-replay-browser@1.27.10': + resolution: {integrity: sha512-AWvAtiQ9/T52DCXS3hcjtHQs4GvZxM7rxgs24DgxqFY2uwCTTnI78le4U7nPWhSrj02YK+3b8y7QN3mm23lHyQ==} + + '@amplitude/plugin-web-vitals-browser@1.1.30': + resolution: {integrity: sha512-nLZk2dTHG8pLd/fFH0zdIhWnu4u+oPc/DKBYXwZ4zk6YKOkl0V+sbDUNGNnZWlOWRykq+0rkOX/WnUyClvMtaQ==} '@amplitude/rrdom@2.0.0-alpha.37': resolution: {integrity: sha512-u4dSnBtlbJ8oU5P/Ywl2RLqvjqWbkl4ScMUbvQA7in4pWcx+0NRN+VVjLZXQcd8Fn7E/rcxjeUh7e7HfwvdasQ==} @@ -1395,8 +1410,8 @@ packages: '@amplitude/rrweb@2.0.0-alpha.37': resolution: {integrity: sha512-jJkSpPYiVgOZB422pb2jOJJn3pvb5E5f9vKK8CEmUlk2mVAl6kPQzW98mb05M65OJFj5nn9tSe9h5r5+Cl93ag==} - '@amplitude/session-replay-browser@1.36.0': - resolution: {integrity: sha512-HZpNRMRAiLbzGF84DzF+ZH5WztJH4tVe2e/FzYJ2r27Sgf2gftCmzCB9pN8BXXcHKYtQK8/Qol+PTmSIzvyvEw==} + '@amplitude/session-replay-browser@1.37.0': + resolution: {integrity: sha512-65KC35dK2yxHoBTDTZeJC8qPchj4lFqTuNjBbH1jaV3hzYoRrGA/xWXLZgxlFvc/7yvcGBbTUW2TeGMAeW6FUg==} '@amplitude/targeting@0.2.0': resolution: {integrity: sha512-/50ywTrC4hfcfJVBbh5DFbqMPPfaIOivZeb5Gb+OGM03QrA+lsUqdvtnKLNuWtceD4H6QQ2KFzPJ5aAJLyzVDA==} @@ -1634,8 +1649,8 @@ packages: '@cucumber/cucumber-expressions@19.0.0': resolution: {integrity: sha512-4FKoOQh2Uf6F6/Ln+1OxuK8LkTg6PyAqekhf2Ix8zqV2M54sH+m7XNJNLhOFOAW/t9nxzRbw2CcvXbCLjcvHZg==} - '@cucumber/cucumber@12.8.0': - resolution: {integrity: sha512-sRG2QMAgCic4Uq1q+5LRzApEHiNGX5rhQY/GuOJZ9BIySrGPA9pevB0imJsZvdzt9scaWyIM3c7dIf4Dp1YQRA==} + '@cucumber/cucumber@12.8.1': + resolution: {integrity: sha512-hCXxiStjbZsRVZlV+CMywkqBtJ6RZTQeXSBZGPHm1YoIOI6YB8pCo0KlnJMmxfKfoeUKagtQMNPnpJBXwhkUjQ==} engines: {node: 20 || 22 || >=24} hasBin: true @@ -1659,8 +1674,8 @@ packages: peerDependencies: '@cucumber/messages': '>=18' - '@cucumber/junit-xml-formatter@0.13.2': - resolution: {integrity: sha512-worYkxjeOWJV+b7WkgJekWgFHlIhbuocnFK3hP+pMYXqZMmkXsxAorYPjeF8KyLnZXajw5fKHS2bM9rQIUI7Zw==} + '@cucumber/junit-xml-formatter@0.13.3': + resolution: {integrity: sha512-w9ujOxiuKDtU6fLzJz+wp4Sgp5Xu6ba7ls00LHJccVmQU0Ba7zs+AHnv3iIgPjKZAQe1w8x93dr8Gaubh7Vqkg==} peerDependencies: '@cucumber/messages': '*' @@ -1678,8 +1693,8 @@ packages: '@cucumber/cucumber': '>=7.0.0' '@cucumber/messages': '*' - '@cucumber/query@14.7.0': - resolution: {integrity: sha512-fiqZ4gMEgYjmbuWproF/YeCdD5y+gD2BqgBIGbpihOsx6UlNsyzoDSfO+Tny0q65DxfK+pHo2UkPyEl7dO7wmQ==} + '@cucumber/query@15.0.1': + resolution: {integrity: sha512-FMfT3orJblRsOxvU2doECBvQmauizYlj+5JsM8atAKKPbnQTj7v2/OrnuykvQpfZNBf19DYbRq1e832vllRP/g==} peerDependencies: '@cucumber/messages': '*' @@ -1702,9 +1717,18 @@ packages: peerDependencies: tailwindcss: '*' + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.9.1': resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emoji-mart/data@1.2.1': resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==} @@ -1940,8 +1964,8 @@ packages: resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-array@0.23.4': - resolution: {integrity: sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/config-helpers@0.2.3': @@ -1952,6 +1976,10 @@ packages: resolution: {integrity: sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/core@0.14.0': resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1968,6 +1996,10 @@ packages: resolution: {integrity: sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/css-tree@4.0.1': resolution: {integrity: sha512-2fCSKRwoUHntYq9J1Lm28s2zeoCSNh1Cbk6Tg7k7ViwOnveIfZwPRFGwBglz+dzw2MHe5w5Fo9+VJfqL9nco2w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -2001,8 +2033,8 @@ packages: resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@3.0.4': - resolution: {integrity: sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==} + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/plugin-kit@0.3.5': @@ -2017,8 +2049,8 @@ packages: resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/plugin-kit@0.7.0': - resolution: {integrity: sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==} + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@floating-ui/core@1.7.5': @@ -2403,20 +2435,26 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@neoconfetti/react@1.0.0': resolution: {integrity: sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==} '@next/env@16.0.0': resolution: {integrity: sha512-s5j2iFGp38QsG1LWRQaE2iUY3h1jc014/melHFfLdrsMJPqxqDQwWNwyQTcNoUSGZlCVZuM7t7JDMmSyRilsnA==} - '@next/env@16.2.3': - resolution: {integrity: sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==} + '@next/env@16.2.4': + resolution: {integrity: sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==} - '@next/eslint-plugin-next@16.2.3': - resolution: {integrity: sha512-nE/b9mht28XJxjTwKs/yk7w4XTaU3t40UHVAky6cjiijdP/SEy3hGsnQMPxmXPTpC7W4/97okm6fngKnvCqVaA==} + '@next/eslint-plugin-next@16.2.4': + resolution: {integrity: sha512-tOX826JJ96gYK/go18sPUgMq9FK1tqxBFfUCEufJb5XIkWFFmpgU7mahJANKGkHs7F41ir3tReJ3Lv5La0RvhA==} - '@next/mdx@16.2.3': - resolution: {integrity: sha512-mm7XNfPagSIcN8jFtozB9toeh5ESES0KCLRoo0gu6xydijvnIrV7dRIK3akNL3Tecc8AHX1FNzYZOZTeFU6RCw==} + '@next/mdx@16.2.4': + resolution: {integrity: sha512-e/3bgla+/oF3vDlndI0eFPa0bnP47HPVA0InsAJi7Jr3DwV8WpEGuOcm/3PdI5/93FfNiBhMVeVHZpm1sFlmJw==} peerDependencies: '@mdx-js/loader': '>=0.15.0' '@mdx-js/react': '>=0.15.0' @@ -2426,54 +2464,54 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@16.2.3': - resolution: {integrity: sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==} + '@next/swc-darwin-arm64@16.2.4': + resolution: {integrity: sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.3': - resolution: {integrity: sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==} + '@next/swc-darwin-x64@16.2.4': + resolution: {integrity: sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.3': - resolution: {integrity: sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==} + '@next/swc-linux-arm64-gnu@16.2.4': + resolution: {integrity: sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.3': - resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==} + '@next/swc-linux-arm64-musl@16.2.4': + resolution: {integrity: sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.3': - resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==} + '@next/swc-linux-x64-gnu@16.2.4': + resolution: {integrity: sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.3': - resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==} + '@next/swc-linux-x64-musl@16.2.4': + resolution: {integrity: sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.3': - resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==} + '@next/swc-win32-arm64-msvc@16.2.4': + resolution: {integrity: sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.3': - resolution: {integrity: sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==} + '@next/swc-win32-x64-msvc@16.2.4': + resolution: {integrity: sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2538,129 +2576,129 @@ packages: resolution: {integrity: sha512-XRO0zi2NIUKq2lUk3T1ecFSld1fMWRKE6naRFGkgkdeosx7IslyUKNv5Dcb5PJTja9tHJoFu0v/7yEpAkrkrTg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@oxc-parser/binding-android-arm-eabi@0.121.0': - resolution: {integrity: sha512-n07FQcySwOlzap424/PLMtOkbS7xOu8nsJduKL8P3COGHKgKoDYXwoAHCbChfgFpHnviehrLWIPX0lKGtbEk/A==} + '@oxc-parser/binding-android-arm-eabi@0.126.0': + resolution: {integrity: sha512-svyoHt25J4741QJ5aa4R+h0iiBeSRt63Lr3aAZcxy2c/NeSE1IfDeMnSij6rIg7EjxkdlXzz613wUjeCeilBNA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxc-parser/binding-android-arm64@0.121.0': - resolution: {integrity: sha512-/Dd1xIXboYAicw+twT2utxPD7bL8qh7d3ej0qvaYIMj3/EgIrGR+tSnjCUkiCT6g6uTC0neSS4JY8LxhdSU/sA==} + '@oxc-parser/binding-android-arm64@0.126.0': + resolution: {integrity: sha512-hPEBRKgplp1mG9GkINFsr4JVMDNrGJLOqfDaadTWpAoTnzYR5Rmv8RMvB3hJZpiNvbk1aacopdHUP1pggMQ/cw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-parser/binding-darwin-arm64@0.121.0': - resolution: {integrity: sha512-A0jNEvv7QMtCO1yk205t3DWU9sWUjQ2KNF0hSVO5W9R9r/R1BIvzG01UQAfmtC0dQm7sCrs5puixurKSfr2bRQ==} + '@oxc-parser/binding-darwin-arm64@0.126.0': + resolution: {integrity: sha512-ccRpu9sdYmznePJQG5halhs0FW5tw5a8zRSoZXOzM1OjoeZ4jiRRruFiPclsD59edoVAK1l83dvfjWz1nQi6lg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-parser/binding-darwin-x64@0.121.0': - resolution: {integrity: sha512-SsHzipdxTKUs3I9EOAPmnIimEeJOemqRlRDOp9LIj+96wtxZejF51gNibmoGq8KoqbT1ssAI5po/E3J+vEtXGA==} + '@oxc-parser/binding-darwin-x64@0.126.0': + resolution: {integrity: sha512-CHB4zVjNSKqx8Fw9pHowzQQnjjuq04i4Ng0Avj+DixlwhwAoMYqlFbocYIlbg+q3zOLGlm7vEHm83jqEMitnyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-parser/binding-freebsd-x64@0.121.0': - resolution: {integrity: sha512-v1APOTkCp+RWOIDAHRoaeW/UoaHF15a60E8eUL6kUQXh+i4K7PBwq2Wi7jm8p0ymID5/m/oC1w3W31Z/+r7HQw==} + '@oxc-parser/binding-freebsd-x64@0.126.0': + resolution: {integrity: sha512-RQ3nEJdcDKBfBjmLJ3Vl1d0KQERPV1P8eUrnBm7+VTYyoaJSPLVFuPg1mlD1hk3n0/879VLFMfusFkBal4ssWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0': - resolution: {integrity: sha512-PmqPQuqHZyFVWA4ycr0eu4VnTMmq9laOHZd+8R359w6kzuNZPvmmunmNJ8ybkm769A0nCoVp3TJ6dUz7B3FYIQ==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.126.0': + resolution: {integrity: sha512-onipc2wCDA7Bauzb4KK1mab0GsEDf4ujiIfWECdnmY/2LlzAoX3xdQRLAUyEDB1kn3yilHBrkmXDdHluyHXxiw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm-musleabihf@0.121.0': - resolution: {integrity: sha512-vF24htj+MOH+Q7y9A8NuC6pUZu8t/C2Fr/kDOi2OcNf28oogr2xadBPXAbml802E8wRAVfbta6YLDQTearz+jw==} + '@oxc-parser/binding-linux-arm-musleabihf@0.126.0': + resolution: {integrity: sha512-5BuJJPohrV5NJ8lmcYOMbfRCUGoYH5J9HZHeuqOLwkHXWAuPMN3X1h8bC/2mWjmosdbfTtmyIdX3spS/TkqKNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm64-gnu@0.121.0': - resolution: {integrity: sha512-wjH8cIG2Lu/3d64iZpbYr73hREMgKAfu7fqpXjgM2S16y2zhTfDIp8EQjxO8vlDtKP5Rc7waZW72lh8nZtWrpA==} + '@oxc-parser/binding-linux-arm64-gnu@0.126.0': + resolution: {integrity: sha512-r2KApRgm2pOJaduRm6GOT8x0whcr67AyejNkSdzPt34GJ+Y3axcXN2mwlTs+8lfO/SSmpO5ZJGYiHYnxEE0jkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-arm64-musl@0.121.0': - resolution: {integrity: sha512-qT663J/W8yQFw3dtscbEi9LKJevr20V7uWs2MPGTnvNZ3rm8anhhE16gXGpxDOHeg9raySaSHKhd4IGa3YZvuw==} + '@oxc-parser/binding-linux-arm64-musl@0.126.0': + resolution: {integrity: sha512-FQ+MMh7MT0Dr/u8+RWmWKlfoeWPQyHDbhhxJShJlYtROXXPHsRs9EvmQOZZ3sx4Nn7JU8NX+oyw2YzQ7anBJcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': - resolution: {integrity: sha512-mYNe4NhVvDBbPkAP8JaVS8lC1dsoJZWH5WCjpw5E+sjhk1R08wt3NnXYUzum7tIiWPfgQxbCMcoxgeemFASbRw==} + '@oxc-parser/binding-linux-ppc64-gnu@0.126.0': + resolution: {integrity: sha512-Wv/T8C98hRQhGTlx2XFyLn5raRMp9U1lOQD+YnXNgAr7wHbJJpZ8mDBU7Rw+M3WytGcGTFcr6kqgfyQeHVtLbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': - resolution: {integrity: sha512-+QiFoGxhAbaI/amqX567784cDyyuZIpinBrJNxUzb+/L2aBRX67mN6Jv40pqduHf15yYByI+K5gUEygCuv0z9w==} + '@oxc-parser/binding-linux-riscv64-gnu@0.126.0': + resolution: {integrity: sha512-DHx1rT1zauW0ZbLHOiQh5AC9Xs3UkWx2XmfZHs+7nnWYr3sagrufoUQC+/XPwwjMIlCFXiFGM0sFh3TyOCZwqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-musl@0.121.0': - resolution: {integrity: sha512-9ykEgyTa5JD/Uhv2sttbKnCfl2PieUfOjyxJC/oDL2UO0qtXOtjPLl7H8Kaj5G7p3hIvFgu3YWvAxvE0sqY+hQ==} + '@oxc-parser/binding-linux-riscv64-musl@0.126.0': + resolution: {integrity: sha512-umDc2mTShH0U2zcEYf8mIJ163seLJNn54ZUZYeI5jD4qlg9izPwoLrC2aNPKlMJTu6u/ysmQWiEvIiaAG+INkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-s390x-gnu@0.121.0': - resolution: {integrity: sha512-DB1EW5VHZdc1lIRjOI3bW/wV6R6y0xlfvdVrqj6kKi7Ayu2U3UqUBdq9KviVkcUGd5Oq+dROqvUEEFRXGAM7EQ==} + '@oxc-parser/binding-linux-s390x-gnu@0.126.0': + resolution: {integrity: sha512-PXXeWayclRtO1pxQEeCpiqIglQdhK2mAI2VX5xnsWdImzSB5GpoQ8TNw7vTCKk2k+GZuxl+q1knncidjCyUP9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-gnu@0.121.0': - resolution: {integrity: sha512-s4lfobX9p4kPTclvMiH3gcQUd88VlnkMTF6n2MTMDAyX5FPNRhhRSFZK05Ykhf8Zy5NibV4PbGR6DnK7FGNN6A==} + '@oxc-parser/binding-linux-x64-gnu@0.126.0': + resolution: {integrity: sha512-wzocjxm34TbB3bFlqG65JiLtvf6ZDg2ZxRkLLbgXwDQUNU+0MPjQN8zy/0jBKNA5fnPLk3XeVdZ7Uin+7+CVkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-musl@0.121.0': - resolution: {integrity: sha512-P9KlyTpuBuMi3NRGpJO8MicuGZfOoqZVRP1WjOecwx8yk4L/+mrCRNc5egSi0byhuReblBF2oVoDSMgV9Bj4Hw==} + '@oxc-parser/binding-linux-x64-musl@0.126.0': + resolution: {integrity: sha512-e83uftP60jmkPs2+CW6T6A1GYzN2H6IumDAiTntv9WyHR73PI3ImHNBkYqnA3ukeKI3xjcCbhSh9QeJWmufxGQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxc-parser/binding-openharmony-arm64@0.121.0': - resolution: {integrity: sha512-R+4jrWOfF2OAPPhj3Eb3U5CaKNAH9/btMveMULIrcNW/hjfysFQlF8wE0GaVBr81dWz8JLgQlsxwctoL78JwXw==} + '@oxc-parser/binding-openharmony-arm64@0.126.0': + resolution: {integrity: sha512-4WiOILHnPrTDY2/L4mE6PZCYwLN1d3ghma6BuTJ452CCgzRMt3uFplCtR+o3r9zdUWJYb370UizpI9CUcWXr1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-parser/binding-wasm32-wasi@0.121.0': - resolution: {integrity: sha512-5TFISkPTymKvsmIlKasPVTPuWxzCcrT8pM+p77+mtQbIZDd1UC8zww4CJcRI46kolmgrEX6QpKO8AvWMVZ+ifw==} + '@oxc-parser/binding-wasm32-wasi@0.126.0': + resolution: {integrity: sha512-Y17hhnrQTrxgAxAyAq401vnN9URsAL4s5AjqpG1NDsXSlhe1yBNnns+rC2P6xcMoitgX5nKH2ryYt9oiFRlzLw==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-parser/binding-win32-arm64-msvc@0.121.0': - resolution: {integrity: sha512-V0pxh4mql4XTt3aiEtRNUeBAUFOw5jzZNxPABLaOKAWrVzSr9+XUaB095lY7jqMf5t8vkfh8NManGB28zanYKw==} + '@oxc-parser/binding-win32-arm64-msvc@0.126.0': + resolution: {integrity: sha512-Znug1u1iRvT4VC3jANz6nhGBHsFwEFMxuimYpJFwMtsB6H5FcEoZRMmH26tHkSTD03JvDmG+gB65W3ajLjPcSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-parser/binding-win32-ia32-msvc@0.121.0': - resolution: {integrity: sha512-4Ob1qvYMPnlF2N9rdmKdkQFdrq16QVcQwBsO8yiPZXof0fHKFF+LmQV501XFbi7lHyrKm8rlJRfQ/M8bZZPVLw==} + '@oxc-parser/binding-win32-ia32-msvc@0.126.0': + resolution: {integrity: sha512-qrw7mx5hFFTxVSXToOA40hpnjgNB/DJprZchtB4rDKNLKqkD3F26HbzaQeH1nxAKej0efSZfJd5Sw3qdtOLGhw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxc-parser/binding-win32-x64-msvc@0.121.0': - resolution: {integrity: sha512-BOp1KCzdboB1tPqoCPXgntgFs0jjeSyOXHzgxVFR7B/qfr3F8r4YDacHkTOUNXtDgM8YwKnkf3rE5gwALYX7NA==} + '@oxc-parser/binding-win32-x64-msvc@0.126.0': + resolution: {integrity: sha512-ibB1s+mPUFXvS7MFJO2jpw/aCNs/P6ifnWlRyTYB+WYBpniOiCcHQQskZneJtwcjQMDRol3RGG3ihoYnzXSY4w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2669,9 +2707,6 @@ packages: resolution: {integrity: sha512-oksjxfqDNmIYMGlIgLzYgnz5YjZax27RtQezsPpKEGo9AC5LOaIGHsivCCeaAWdCtPnRyjZXM/7svreCC8kZVQ==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.121.0': - resolution: {integrity: sha512-CGtOARQb9tyv7ECgdAlFxi0Fv7lmzvmlm2rpD/RdijOO9rfk/JvB1CjT8EnoD+tjna/IYgKKw3IV7objRb+aYw==} - '@oxc-project/types@0.126.0': resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} @@ -3375,32 +3410,32 @@ packages: rollup: optional: true - '@sentry-internal/browser-utils@10.48.0': - resolution: {integrity: sha512-SCiTLBXzugFKxev6NoKYBIhQoDk0gUh0AVVVepCBqfCJiWBG01Zvv0R5tCVohr4cWRllkQ8mlBdNQd/I7s9tdA==} + '@sentry-internal/browser-utils@10.49.0': + resolution: {integrity: sha512-n0QRx0Ysx6mPfIydTkz7VP0FmwM+/EqMZiRqdsU3aTYsngE9GmEDV0OL1bAy6a8N/C1xf9vntkuAtj6N/8Z51w==} engines: {node: '>=18'} - '@sentry-internal/feedback@10.48.0': - resolution: {integrity: sha512-tGkEyOM1HDS9qebDphUMEnyk3qq/50AnuTBiFmMJyjNzowylVGmRRk0sr3xkmbVHCDXQCiYnDmSVlJ2x4SDMrQ==} + '@sentry-internal/feedback@10.49.0': + resolution: {integrity: sha512-JNsUBGv0faCFE7MeZUH99Y9lU9qq3LBALbLxpE1x7ngNrQnVYRlcFgdqaD/btNBKr8awjYL8gmcSkHBWskGqLQ==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@10.48.0': - resolution: {integrity: sha512-9nWuN2z4O+iwbTfuYV5ZmngBgJU/ZxfOo47A5RJP3Nu/kl59aJ1lUhILYOKyeNOIC/JyeERmpIcTxnlPXQzZ3Q==} + '@sentry-internal/replay-canvas@10.49.0': + resolution: {integrity: sha512-7D/NrgH1Qwx5trDYaaTSSJmCb1yVQQLqFG4G/S9x2ltzl9876lSGJL8UeW8ReNQgF3CDAcwbmm/9aXaVSBUNZA==} engines: {node: '>=18'} - '@sentry-internal/replay@10.48.0': - resolution: {integrity: sha512-sevRTePfuk4PNuz9KAKpmTZEomAU0aLXyIhOwA0OnUDdxPhkY8kq5lwDbuxTHv6DQUjUX3YgFbY45VH1JEqHKA==} + '@sentry-internal/replay@10.49.0': + resolution: {integrity: sha512-IEy4lwHVMiRE3JAcn+kFKjsTgalDOCSTf20SoFd+nkt6rN/k1RDyr4xpdfF//Kj3UdeTmbuibYjK5H/FLhhnGg==} engines: {node: '>=18'} - '@sentry/browser@10.48.0': - resolution: {integrity: sha512-4jt2zX2ExgFcNe2x+W+/k81fmDUsOrquGtt028CiGuDuma6kEsWBI4JbooT1jhj2T+eeUxe3YGbM23Zhh7Ghhw==} + '@sentry/browser@10.49.0': + resolution: {integrity: sha512-bGCHc+wK2Dx67YoSbmtlt04alqWfQ+dasD/GVipVOq50gvw/BBIDHTEWRJEjACl+LrvszeY54V+24p8z4IgysA==} engines: {node: '>=18'} - '@sentry/core@10.48.0': - resolution: {integrity: sha512-h8F+fXVwYC9ro5ZaO8V+v3vqc0awlXHGblEAuVxSGgh4IV/oFX+QVzXeDTTrFOFS6v/Vn5vAyu240eJrJAS6/g==} + '@sentry/core@10.49.0': + resolution: {integrity: sha512-UaFeum3LUM1mB0d67jvKnqId1yWQjyqmaDV6kWngG03x+jqXb08tJdGpSoxjXZe13jFBbiBL/wKDDYIK7rCK4g==} engines: {node: '>=18'} - '@sentry/react@10.48.0': - resolution: {integrity: sha512-uc93vKjmu6gNns+JAX4qquuxWpAMit0uGPA1TYlMjct9NG1uX3TkDPJAr9Pgd1lOXx8mKqCmj5fK33QeExMpPw==} + '@sentry/react@10.49.0': + resolution: {integrity: sha512-WdfJve0orTiumr25Ozgs2p2KaJR9xV82Z5V9IYBi0TadsurSWK6xI6SAFjw84tQht9Fp8q4UCn3QYCnApF4BfA==} engines: {node: '>=18'} peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x @@ -3634,69 +3669,69 @@ packages: zod: optional: true - '@tailwindcss/node@4.2.2': - resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} - '@tailwindcss/oxide-android-arm64@4.2.2': - resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.2': - resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.2': - resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.2': - resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -3707,32 +3742,32 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.2': - resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} engines: {node: '>= 20'} - '@tailwindcss/postcss@4.2.2': - resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} + '@tailwindcss/postcss@4.2.4': + resolution: {integrity: sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==} '@tailwindcss/typography@0.5.19': resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' - '@tailwindcss/vite@4.2.2': - resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -3780,8 +3815,8 @@ packages: engines: {node: '>=18'} hasBin: true - '@tanstack/eslint-plugin-query@5.99.0': - resolution: {integrity: sha512-jVp1AEL7S7BeuQvH5SN1F5UdrNW/AbryKDeWUUMeAKNzh9C+Ik/bRSa/HeuJLlmaN+WOUkdDFbtCK0go7BxnUQ==} + '@tanstack/eslint-plugin-query@5.99.2': + resolution: {integrity: sha512-xiazL4CWOHJRDDgs5ZkfW98qlEAisakFDKh1Djc3BIk84tsvt3ow52AC2EiWSMY1q13IB4UI4jSo7yXlC3NL6g==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ^5.4.0 || ^6.0.0 @@ -3789,11 +3824,11 @@ packages: typescript: optional: true - '@tanstack/form-core@1.29.0': - resolution: {integrity: sha512-uyeKEdJBfbj0bkBSwvSYVRtWLOaXvfNX3CeVw1HqGOXVLxpBBGAqWdYLc+UoX/9xcoFwFXrjR9QqMPzvwm2yyQ==} + '@tanstack/form-core@1.29.1': + resolution: {integrity: sha512-NIYPO36eEu7nSWvMpbFDQaBWyVtnH/C8fsZ3/XpJUT4uOWgmxsiUvHGbTbDNIQTXAKIkhwEl0sUrqBNn2SfUnw==} - '@tanstack/form-devtools@0.2.21': - resolution: {integrity: sha512-8mxR1/QDw37mNVSFsr4ZN8+bdamH9LU1/iQ3I7/sfTzFmMsNzUOysX3OZf053eaS4Gaw44PT0pH7U0FWD98QKw==} + '@tanstack/form-devtools@0.2.22': + resolution: {integrity: sha512-hMrKwu+73O2LeHj78vi48oaAH4jZi/U92hrHmkvxDy3E72c+PbxDJBbM9rXUK4h0GPbOzfaZ235SruJ0lfuOYA==} peerDependencies: solid-js: 1.9.11 @@ -3801,11 +3836,11 @@ packages: resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==} engines: {node: '>=18'} - '@tanstack/query-core@5.99.0': - resolution: {integrity: sha512-3Jv3WQG0BCcH7G+7lf/bP8QyBfJOXeY+T08Rin3GZ1bshvwlbPt7NrDHMEzGdKIOmOzvIQmxjk28YEQX60k7pQ==} + '@tanstack/query-core@5.99.2': + resolution: {integrity: sha512-1HunU0bXVsR1ZJMZbcOPE6VtaBJxsW809RE9xPe4Gz7MlB0GWwQvuTPhMoEmQ/hIzFKJ/DWAuttIe7BOaWx0tA==} - '@tanstack/query-devtools@5.99.0': - resolution: {integrity: sha512-m4ufXaJ8FjWXw7xDtyzE/6fkZAyQFg9WrbMrUpt8ZecRJx58jiFOZ2lxZMphZdIpAnIeto/S8stbwLKLusyckQ==} + '@tanstack/query-devtools@5.99.2': + resolution: {integrity: sha512-TEF1d+RYO9l8oeCwgzmOHIgKwAzXQmw2s/ny2bW8qeg2OMkkLjALfVEivgCMR3OL/jVdMmeTPX56WrV+uvYJFg==} '@tanstack/react-devtools@0.10.2': resolution: {integrity: sha512-1BmZyxOrI5SqmRJ5MgkYZNNdnlLsJxQRI2YgorrAvcF2MxK6x5RcuStvD8+YlXoMw3JtNukPxoITirKAnKYDQA==} @@ -3816,13 +3851,13 @@ packages: react: '>=16.8' react-dom: '>=16.8' - '@tanstack/react-form-devtools@0.2.21': - resolution: {integrity: sha512-WBQ7NOcb3FM9UA4juZVyWUyJkyl62vHFbEBybZuvBFw3wq/v9pDGS01Ye8kepGXDg1+LQsOOxyDR65AKsdqSYQ==} + '@tanstack/react-form-devtools@0.2.22': + resolution: {integrity: sha512-CXa+U6QrF8QOGL+sCIIcwzHb1K+hfNjBA5PwSmxm32Oxpu8fK/60M3SbE9UM9439MR/GQiIoeBW2FFyKh73apw==} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-form@1.29.0': - resolution: {integrity: sha512-jj425NNX0QKqbUzqSNiYI3HCPHSk2df47acXCJyXczWOTmG81ECZGkgofgqamFsSU9kMiH6Di5RLUnftrlhWSw==} + '@tanstack/react-form@1.29.1': + resolution: {integrity: sha512-hVHk4g0phd0HxRsv2ry6Xt8BqmalT55Q3cokhJBCC1St0hcGZhgwJJbohm9atao45BPG9e55DGvtbwExqZe35g==} peerDependencies: '@tanstack/react-start': '*' react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3830,14 +3865,14 @@ packages: '@tanstack/react-start': optional: true - '@tanstack/react-query-devtools@5.99.0': - resolution: {integrity: sha512-CqqX7LCU9yOfCY/vBURSx2YSD83ryfX+QkfkaKionTfg1s2Hdm572Ro99gW3QPoJjzvsj1HM4pnN4nbDy3MXKA==} + '@tanstack/react-query-devtools@5.99.2': + resolution: {integrity: sha512-8txkK9A9XBNTB8RoxVgfp6W3qwBr25tNP10L4yu3KuyhAdEvccECfIRzesSwMVk/wpVVioAr+hbMtUkMMF+WVw==} peerDependencies: - '@tanstack/react-query': ^5.99.0 + '@tanstack/react-query': ^5.99.2 react: ^18 || ^19 - '@tanstack/react-query@5.99.0': - resolution: {integrity: sha512-OY2bCqPemT1LlqJ8Y2CUau4KELnIhhG9Ol3ZndPbdnB095pRbPo1cHuXTndg8iIwtoHTgwZjyaDnQ0xD0mYwAw==} + '@tanstack/react-query@5.99.2': + resolution: {integrity: sha512-vM91UEe45QUS9ED6OklsVL15i8qKcRqNwpWzPTVWvRPRSEgDudDgHpvyTjcdlwHcrKNa80T+xXYcchT2noPnZA==} peerDependencies: react: ^18 || ^19 @@ -3847,8 +3882,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-virtual@3.13.23': - resolution: {integrity: sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==} + '@tanstack/react-virtual@3.13.24': + resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3856,8 +3891,8 @@ packages: '@tanstack/store@0.9.3': resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} - '@tanstack/virtual-core@3.13.23': - resolution: {integrity: sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==} + '@tanstack/virtual-core@3.14.0': + resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} '@teppeis/multimaps@3.0.0': resolution: {integrity: sha512-ID7fosbc50TbT0MK0EG12O+gAP3W3Aa/Pz4DaTtQtEvlc9Odaqi0de+xuZ7Li2GtK4HzEX7IuRWS/JmZLksR3Q==} @@ -3892,22 +3927,22 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tsslint/cli@3.0.3': - resolution: {integrity: sha512-Pt1AuEZoh+dK4QYt95oCjBdBp2h2iYY9pSerf9BTLgfsjeyEsNk7Juhn51sFlAuEnWDNvI8mLULzsIkayd0nUQ==} + '@tsslint/cli@3.0.4': + resolution: {integrity: sha512-jvSYZEJKhDp02CyvLe7thGYp/uMW860kC8hDIMnZAGp3JMDkM2dU1kl550li4qiYXFkS8v5AU1nR2RyIn3khvw==} engines: {node: '>=22.6.0'} hasBin: true peerDependencies: typescript: '*' - '@tsslint/compat-eslint@3.0.3': - resolution: {integrity: sha512-UGWrE4fu8fUCLkc+zMQNsEfuEkGHjndpa5oSQmzhmo9BQJYAqqH1s2kGIiDsAYwaQTUts4SjclXaITq3pZhkrA==} + '@tsslint/compat-eslint@3.0.4': + resolution: {integrity: sha512-zWurlYWaSfK62uf5n7GMa0C7pcYOXbYjMeBfd3w0RmCZzk5gBhNSJdSNXNmbDXUuM/3RH03PpqHuUIktCGB52g==} - '@tsslint/config@3.0.3': - resolution: {integrity: sha512-3yFyM4Sj+0LxwmcokwNPuS9pWUBMIhO8vwHiG4vGuquTvF4cgZqDPyQ3GN4hDb5qAZ56iqYtMoBEiSZXlJDYPQ==} + '@tsslint/config@3.0.4': + resolution: {integrity: sha512-2VfGdG35wrcosUxxsoUD46LOI1lEJWhQFpDROhos2JOwwVPIQqp66hl9MOYjkBpt8zYVWvdcDWIOIT9QIpDL3A==} engines: {node: '>=22.6.0'} hasBin: true peerDependencies: - '@tsslint/compat-eslint': 3.0.0-alpha.0 + '@tsslint/compat-eslint': ^3.0.0 tsl: ^1.0.28 peerDependenciesMeta: '@tsslint/compat-eslint': @@ -3915,12 +3950,12 @@ packages: tsl: optional: true - '@tsslint/core@3.0.3': - resolution: {integrity: sha512-EpCKw34f2XyypH5xlxKCwnTgPGpZxbPXfvpwddT3DCxsIzUDJY4SpVJULAZFPAjJd49vopG0kNhXn0C/b+kHcg==} + '@tsslint/core@3.0.4': + resolution: {integrity: sha512-hzvO/8zZfds9k7ZREyE5h2pnKkukZsAD81F7rq/k9AOv//Wmi2OxXyxmhmv98/ZoieOK5nSrrzh8+mh7GtkrEw==} engines: {node: '>=22.6.0'} - '@tsslint/types@3.0.3': - resolution: {integrity: sha512-3Jlb5UTPrzqu1D1qOrzjwy0QW2n41A1+ILKvzgViFrtiTwurM5Tav6V7Y4AFxO0xatCA0VHAzzifK0r5znaKbw==} + '@tsslint/types@3.0.4': + resolution: {integrity: sha512-z/LXFUSGCxrh/WfkVmlyRwCVjAr2H1/v6EDvVTuXX/3ZEO+Ss9UqgEGgnTnQn3TLSLJa2pEaIY3Hsz0Y9TsuyA==} '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -4142,6 +4177,14 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/eslint-plugin@8.59.0': + resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/parser@8.58.2': resolution: {integrity: sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4149,22 +4192,45 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/parser@8.59.0': + resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.58.2': resolution: {integrity: sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.59.0': + resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.58.2': resolution: {integrity: sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.59.0': + resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.58.2': resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/tsconfig-utils@8.59.0': + resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/type-utils@8.58.2': resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4172,16 +4238,33 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/type-utils@8.59.0': + resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/types@8.58.2': resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.59.0': + resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.58.2': resolution: {integrity: sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/typescript-estree@8.59.0': + resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.58.2': resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4189,47 +4272,58 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.59.0': + resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@8.58.2': resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-CDgxIPvAWRCfOiQKvSk4wUkAoRW4Cy6vfAUBPNHSeLalIt43ToF0LOAsa5uLyRGsftjfMYY0A4qFOmgDvBhgzQ==} + '@typescript-eslint/visitor-keys@8.59.0': + resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-W/lGgoEfbdI/QWYqcNP0fSa4DHQKKEMLzDPsE6fA64zmfCNsTO9M7ttK0acKiLsGB16pr0lubuMDRNN5kXyQ8w==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-oiMmUtNMaqBh+eUogX53ichcEf7d+7upC0qa7xS9zWl85XEPKlrZCZpZ79yixw1PkdpjqJJigI11bmCi/JVv+g==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-6tZ2yAcKLBIghwKyC74vDqb/7rB99fTpERv9f64iA1tMh6l+WHIuQb6z3mIFVOYBIl2pN9CYasURLroKYtUz1w==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-hPKanfs9c+7953gIYw13CNxN0HqFAOfJjnWk4SHqSBe3Pj9pxoeJvvRWlofp5C833eOZK6gZB7ll0/uNb0djtA==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-7HL4E7kP0ociYB8R4+QuIbzfT3pjdesNY+ax/q6fP3IMd3/QNAL/qsm/NaokjXke+I7uYxKqQ8Qo/t5MSv/r+A==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-0lSXBzBVsxIGrFv/PxoswzMptsnU6BgSk7GMAUt/o1dVw36R2XrSs538vwKnujaJwt4iIdMS0uGdpUC5s9jkzQ==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-EWP1Jq2I8MMSkoF9D6ztXgRmnUy2KcaZfL9FYcdm3Am6ZYuI6/SCR3HVIVYbaixAJXe/qUh5MN3LzJbl/4hefQ==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-8Cr477HRmHZ5YyLfikNvw7qp3/WmnRjzIzJhUDrAx5173OBe8BdyV9jPemFHKDPqwI1AUMTijvptOFoQE7429w==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-fDqkLf2Hv7X1Cy1B5OMcljPt/+8GpnTxFM9rDCFrYAPgOolIQJ9qwkb+xGfvAtxkkE5sZIvGPcqjP9PWQHt2qw==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-ulJD9ZbIQyTBIDx8zzAzQLtbvQDGHSWrNRgkgBU5Os2NTYADQRco4pU747R9wZPMLopy3IeNck6m8vwPoYMk1g==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-l1tDnyNQSqxFkKz683dD8EORQtcQqZyWkTDnRtHmaPg2mTRxhxSekL/HcsHx/1/DoGTfl310O+CmXzd2mTq3pQ==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-x7DsSXnLQBf5XBBR8luHf1Nc/T1eByUmrOSEThW6825UB7lHoPlqKdhIoUNnTnS4nXQMxLwcusD4P1EP23GPJw==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-VQbDQlp1bjV5nnHagQLXQAhid3S48l1OToIBjvqlw18s0V0YSgoyNL6E/rE7FBdkGrTLf/rtKjo42IZnt3tvqA==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260413.1': - resolution: {integrity: sha512-twzr3V4QLEbXaESuI2DqdzutOVFGpkY3VZDR9sF8YlLsAXkwyQvZo58cKM77mZcsHoCR4lCYcdTatWTTa/+8tw==} + '@typescript/native-preview@7.0.0-dev.20260422.1': + resolution: {integrity: sha512-8CR8zHFlLpSL5OXY4Wbz2DmiDOoat1JBMkydZUHwQIS4cpoTN7SHjk2BN8i51XHUy0jMF5airL0TlY3GOfZmKg==} hasBin: true '@ungap/structured-clone@1.3.0': @@ -4297,10 +4391,10 @@ packages: react-server-dom-webpack: optional: true - '@vitest/coverage-v8@4.1.4': - resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} peerDependencies: - '@vitest/browser': 4.1.4 + '@vitest/browser': 4.1.5 peerDependenciesMeta: '@vitest/browser': optional: true @@ -4324,8 +4418,8 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@4.1.4': - resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} @@ -4333,8 +4427,8 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@4.1.4': - resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} '@voidzero-dev/vite-plus-core@0.1.19': resolution: {integrity: sha512-BTmz50juSDolIN4Vtu5iVaPONV1XSrMB5V+9IoBhhxdogfvp7PBhaHuAcPjTN2RTVowhLZXoo8mn+aHjq//bkw==} @@ -4702,8 +4796,8 @@ packages: caniuse-lite@1.0.30001781: resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} - canvas@3.2.2: - resolution: {integrity: sha512-duEt4h1HHu9sJZyVKfLRXR6tsKPY7cEELzxSRJkwddOXYvQT3P/+es98SV384JA0zMOZ5s+9gatnGfM6sL4Drg==} + canvas@3.2.3: + resolution: {integrity: sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==} engines: {node: ^18.12.0 || >= 20.9.0} capital-case@1.0.4: @@ -5193,8 +5287,8 @@ packages: resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==} engines: {node: '>=20'} - dompurify@3.4.0: - resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} + dompurify@3.4.1: + resolution: {integrity: sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -5288,8 +5382,8 @@ packages: es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} - es-toolkit@1.45.1: - resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} + es-toolkit@1.46.0: + resolution: {integrity: sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA==} esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -5499,8 +5593,8 @@ packages: peerDependencies: eslint: '>=9.38.0' - eslint-plugin-sonarjs@4.0.2: - resolution: {integrity: sha512-BTcT1zr1iTbmJtVlcesISwnXzh+9uhf9LEOr+RRNf4kR8xA0HQTPft4oiyOCzCOGKkpSJxjR8ZYF6H7VPyplyw==} + eslint-plugin-sonarjs@4.0.3: + resolution: {integrity: sha512-5drkJKLC9qQddIiaATV0e8+ygbUc7b0Ti6VB7M2d3jmKNh3X0RaiIJYTs3dr9xnlhlrxo+/s1FoO3Jgv6O/c7g==} peerDependencies: eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -5577,8 +5671,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.0: - resolution: {integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==} + eslint@10.2.1: + resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -5790,6 +5884,9 @@ packages: get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -5931,8 +6028,8 @@ packages: i18next-resources-to-backend@1.2.1: resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} - i18next@26.0.4: - resolution: {integrity: sha512-gXF7U9bfioXPLv7mw8Qt2nfO7vij5MyINvPgVv99pX3fL1Y01pw2mKBFrlYpRxRCl2wz3ISenj6VsMJT2isfuA==} + i18next@26.0.6: + resolution: {integrity: sha512-A4U6eCXodIbrhf8EarRurB9/4ebyaurH4+fu4gig9bqxmpSt+fCAFm/GpRQDcN1Xzu/LdFCx4nYHsnM1edIIbg==} peerDependencies: typescript: ^5 || ^6 peerDependenciesMeta: @@ -6188,8 +6285,8 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - knip@6.4.1: - resolution: {integrity: sha512-Ry+ywmDFSZvKp/jx7LxMgsZWRTs931alV84e60lh0Stf6kSRYqSIUTkviyyDFRcSO3yY1Kpbi83OirN+4lA2Xw==} + knip@6.6.1: + resolution: {integrity: sha512-SOmqh25vuAfdynGoDr/kMCxIuD5+PkMIfMSGQeMqfrxwuPTANvJKcVttLgGZjjkATALqukSe/hhDVqcwNkf92g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -6199,8 +6296,8 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - ky@2.0.0: - resolution: {integrity: sha512-KzI4Vz5AbZFAUFYGx28PCSfFWUo6/qj9Br/P6KRwDieE1xfdz0tIONepJcLw/1xLocN13GgvfJGasa+pfSkbHg==} + ky@2.0.2: + resolution: {integrity: sha512-/GmXpo9F9W+f8n4Ivr2iH+7h7wL7jLbLKWkMlpflcCRb6kGjBfTlASEXaZ9qUgNTn4VgS0P2pwxxzQ4EM6Ulgg==} engines: {node: '>=22'} lamejs@1.2.1: @@ -6341,8 +6438,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loro-crdt@1.10.8: - resolution: {integrity: sha512-GvH8fSJST1VDHRGzlQml80pBYoFbIP4ULeV1S8fD4ffmA8m+icoPORyVUW2AkJBY3dxKIcMMn0WqaJmpCmnbkQ==} + loro-crdt@1.11.1: + resolution: {integrity: sha512-R+Ksyy2FPYoOfJAkVY6BqGk11AtlgWZ1B91V/G7TaQxitxuvUvMd1URhO33LYfFUIT2CSn0Nikl+bbRZ2RGuZg==} loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -6608,6 +6705,10 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -6683,8 +6784,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.2.3: - resolution: {integrity: sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==} + next@16.2.4: + resolution: {integrity: sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -6785,8 +6886,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - oxc-parser@0.121.0: - resolution: {integrity: sha512-ek9o58+SCv6AV7nchiAcUJy1DNE2CC5WRdBcO0mF+W4oRjNQfPO7b3pLjTHSFECpHkKGOZSQxx3hk8viIL5YCg==} + oxc-parser@0.126.0: + resolution: {integrity: sha512-FktCvLby/mOHyuijZt22+nOt10dS24gGUZE3XwIbUg7Kf4+rer3/5T7RgwzazlNuVsCjPloZ3p8E+4ONT3A8Kw==} engines: {node: ^20.19.0 || >=22.12.0} oxc-resolver@11.19.1: @@ -6984,6 +7085,10 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.9: resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} engines: {node: ^10 || ^12 || >=14} @@ -7638,8 +7743,8 @@ packages: tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} - tailwindcss@4.2.2: - resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} tapable@2.3.2: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} @@ -7816,8 +7921,8 @@ packages: resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==} engines: {node: '>=20'} - typescript@6.0.2: - resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -8289,27 +8394,28 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@amplitude/analytics-browser@2.39.0': + '@amplitude/analytics-browser@2.41.0': dependencies: - '@amplitude/analytics-core': 2.45.0 - '@amplitude/plugin-autocapture-browser': 1.25.2 - '@amplitude/plugin-custom-enrichment-browser': 0.1.4 - '@amplitude/plugin-network-capture-browser': 1.9.13 - '@amplitude/plugin-page-url-enrichment-browser': 0.7.5 - '@amplitude/plugin-page-view-tracking-browser': 2.9.6 - '@amplitude/plugin-web-vitals-browser': 1.1.28 + '@amplitude/analytics-core': 2.47.0 + '@amplitude/plugin-autocapture-browser': 1.26.0 + '@amplitude/plugin-custom-enrichment-browser': 0.1.6 + '@amplitude/plugin-event-property-attribution-browser': 0.1.1 + '@amplitude/plugin-network-capture-browser': 1.9.15 + '@amplitude/plugin-page-url-enrichment-browser': 0.7.7 + '@amplitude/plugin-page-view-tracking-browser': 2.10.1 + '@amplitude/plugin-web-vitals-browser': 1.1.30 tslib: 2.8.1 - '@amplitude/analytics-client-common@2.4.43': + '@amplitude/analytics-client-common@2.4.45': dependencies: '@amplitude/analytics-connector': 1.6.4 - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 '@amplitude/analytics-types': 2.11.1 tslib: 2.8.1 '@amplitude/analytics-connector@1.6.4': {} - '@amplitude/analytics-core@2.45.0': + '@amplitude/analytics-core@2.47.0': dependencies: '@amplitude/analytics-connector': 1.6.4 '@types/zen-observable': 0.8.3 @@ -8323,48 +8429,53 @@ snapshots: dependencies: js-base64: 3.7.8 - '@amplitude/plugin-autocapture-browser@1.25.2': + '@amplitude/plugin-autocapture-browser@1.26.0': dependencies: - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 tslib: 2.8.1 - '@amplitude/plugin-custom-enrichment-browser@0.1.4': + '@amplitude/plugin-custom-enrichment-browser@0.1.6': dependencies: - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 tslib: 2.8.1 - '@amplitude/plugin-network-capture-browser@1.9.13': + '@amplitude/plugin-event-property-attribution-browser@0.1.1': dependencies: - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 tslib: 2.8.1 - '@amplitude/plugin-page-url-enrichment-browser@0.7.5': + '@amplitude/plugin-network-capture-browser@1.9.15': dependencies: - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 tslib: 2.8.1 - '@amplitude/plugin-page-view-tracking-browser@2.9.6': + '@amplitude/plugin-page-url-enrichment-browser@0.7.7': dependencies: - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 tslib: 2.8.1 - '@amplitude/plugin-session-replay-browser@1.27.7(@amplitude/rrweb@2.0.0-alpha.37)': + '@amplitude/plugin-page-view-tracking-browser@2.10.1': dependencies: - '@amplitude/analytics-client-common': 2.4.43 - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 + tslib: 2.8.1 + + '@amplitude/plugin-session-replay-browser@1.27.10(@amplitude/rrweb@2.0.0-alpha.37)': + dependencies: + '@amplitude/analytics-client-common': 2.4.45 + '@amplitude/analytics-core': 2.47.0 '@amplitude/analytics-types': 2.11.1 '@amplitude/rrweb-plugin-console-record': 2.0.0-alpha.36(@amplitude/rrweb@2.0.0-alpha.37) '@amplitude/rrweb-record': 2.0.0-alpha.36 - '@amplitude/session-replay-browser': 1.36.0(@amplitude/rrweb@2.0.0-alpha.37) + '@amplitude/session-replay-browser': 1.37.0(@amplitude/rrweb@2.0.0-alpha.37) idb-keyval: 6.2.2 tslib: 2.8.1 transitivePeerDependencies: - '@amplitude/rrweb' - rollup - '@amplitude/plugin-web-vitals-browser@1.1.28': + '@amplitude/plugin-web-vitals-browser@1.1.30': dependencies: - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-core': 2.47.0 tslib: 2.8.1 web-vitals: 5.1.0 @@ -8388,7 +8499,7 @@ snapshots: '@amplitude/rrweb-snapshot@2.0.0-alpha.37': dependencies: - postcss: 8.5.9 + postcss: 8.5.10 '@amplitude/rrweb-types@2.0.0-alpha.36': {} @@ -8409,10 +8520,10 @@ snapshots: base64-arraybuffer: 1.0.2 mitt: 3.0.1 - '@amplitude/session-replay-browser@1.36.0(@amplitude/rrweb@2.0.0-alpha.37)': + '@amplitude/session-replay-browser@1.37.0(@amplitude/rrweb@2.0.0-alpha.37)': dependencies: - '@amplitude/analytics-client-common': 2.4.43 - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-client-common': 2.4.45 + '@amplitude/analytics-core': 2.47.0 '@amplitude/analytics-types': 2.11.1 '@amplitude/experiment-core': 0.7.2 '@amplitude/rrweb-packer': 2.0.0-alpha.36 @@ -8430,56 +8541,56 @@ snapshots: '@amplitude/targeting@0.2.0': dependencies: - '@amplitude/analytics-client-common': 2.4.43 - '@amplitude/analytics-core': 2.45.0 + '@amplitude/analytics-client-common': 2.4.45 + '@amplitude/analytics-core': 2.47.0 '@amplitude/analytics-types': 2.11.1 '@amplitude/experiment-core': 0.7.2 idb: 8.0.0 tslib: 2.8.1 - '@antfu/eslint-config@8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@next/eslint-plugin-next@16.2.3)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)))(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@antfu/eslint-config@8.2.0(@eslint-react/eslint-plugin@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@next/eslint-plugin-next@16.2.4)(@types/node@25.6.0)(@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3))(@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(eslint-plugin-react-refresh@0.5.2(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 1.2.0 - '@e18e/eslint-plugin': 0.3.0(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1)) - '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.2.0(jiti@2.6.1)) + '@e18e/eslint-plugin': 0.3.0(eslint@10.2.1(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1)) + '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.2.1(jiti@2.6.1)) '@eslint/markdown': 8.0.1 - '@stylistic/eslint-plugin': 5.10.0(eslint@10.2.0(jiti@2.6.1)) - '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/parser': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@vitest/eslint-plugin': 1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.2.1(jiti@2.6.1)) + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@vitest/eslint-plugin': 1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.1(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) ansis: 4.2.0 cac: 7.0.0 - eslint: 10.2.0(jiti@2.6.1) - eslint-config-flat-gitignore: 2.3.0(eslint@10.2.0(jiti@2.6.1)) + eslint: 10.2.1(jiti@2.6.1) + eslint-config-flat-gitignore: 2.3.0(eslint@10.2.1(jiti@2.6.1)) eslint-flat-config-utils: 3.1.0 - eslint-merge-processors: 2.0.0(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-antfu: 3.2.2(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-command: 3.5.2(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-import-lite: 0.6.0(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-jsdoc: 62.9.0(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-jsonc: 3.1.2(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-n: 17.24.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + eslint-merge-processors: 2.0.0(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-antfu: 3.2.2(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-command: 3.5.2(@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3))(@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-import-lite: 0.6.0(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-jsdoc: 62.9.0(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-jsonc: 3.1.2(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-n: 17.24.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-perfectionist: 5.8.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-pnpm: 1.6.0(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-regexp: 3.1.0(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-toml: 1.3.1(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-unicorn: 64.0.0(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)) - eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))) - eslint-plugin-yml: 3.3.1(eslint@10.2.0(jiti@2.6.1)) - eslint-processor-vue-blocks: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-perfectionist: 5.8.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-pnpm: 1.6.0(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-regexp: 3.1.0(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-toml: 1.3.1(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-unicorn: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.1(jiti@2.6.1)))(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.1(jiti@2.6.1))) + eslint-plugin-yml: 3.3.1(eslint@10.2.1(jiti@2.6.1)) + eslint-processor-vue-blocks: 2.0.0(eslint@10.2.1(jiti@2.6.1)) globals: 17.5.0 local-pkg: 1.1.2 parse-gitignore: 2.0.0 toml-eslint-parser: 1.0.3 - vue-eslint-parser: 10.4.0(eslint@10.2.0(jiti@2.6.1)) + vue-eslint-parser: 10.4.0(eslint@10.2.1(jiti@2.6.1)) yaml-eslint-parser: 2.0.0 optionalDependencies: - '@eslint-react/eslint-plugin': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@next/eslint-plugin-next': 16.2.3 - eslint-plugin-react-refresh: 0.5.2(eslint@10.2.0(jiti@2.6.1)) + '@eslint-react/eslint-plugin': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@next/eslint-plugin-next': 16.2.4 + eslint-plugin-react-refresh: 0.5.2(eslint@10.2.1(jiti@2.6.1)) transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -8756,7 +8867,7 @@ snapshots: dependencies: regexp-match-indices: 1.0.2 - '@cucumber/cucumber@12.8.0': + '@cucumber/cucumber@12.8.1': dependencies: '@cucumber/ci-environment': 13.0.0 '@cucumber/cucumber-expressions': 19.0.0 @@ -8764,10 +8875,10 @@ snapshots: '@cucumber/gherkin-streams': 6.0.0(@cucumber/gherkin@38.0.0)(@cucumber/message-streams@4.1.1(@cucumber/messages@32.2.0))(@cucumber/messages@32.2.0) '@cucumber/gherkin-utils': 11.0.0 '@cucumber/html-formatter': 23.0.0(@cucumber/messages@32.2.0) - '@cucumber/junit-xml-formatter': 0.13.2(@cucumber/messages@32.2.0) + '@cucumber/junit-xml-formatter': 0.13.3(@cucumber/messages@32.2.0) '@cucumber/message-streams': 4.1.1(@cucumber/messages@32.2.0) '@cucumber/messages': 32.2.0 - '@cucumber/pretty-formatter': 1.0.1(@cucumber/cucumber@12.8.0)(@cucumber/messages@32.2.0) + '@cucumber/pretty-formatter': 1.0.1(@cucumber/cucumber@12.8.1)(@cucumber/messages@32.2.0) '@cucumber/tag-expressions': 9.1.0 assertion-error-formatter: 3.0.0 capital-case: 1.0.4 @@ -8822,10 +8933,10 @@ snapshots: dependencies: '@cucumber/messages': 32.2.0 - '@cucumber/junit-xml-formatter@0.13.2(@cucumber/messages@32.2.0)': + '@cucumber/junit-xml-formatter@0.13.3(@cucumber/messages@32.2.0)': dependencies: '@cucumber/messages': 32.2.0 - '@cucumber/query': 14.7.0(@cucumber/messages@32.2.0) + '@cucumber/query': 15.0.1(@cucumber/messages@32.2.0) '@teppeis/multimaps': 3.0.0 luxon: 3.7.2 xmlbuilder: 15.1.1 @@ -8840,16 +8951,16 @@ snapshots: class-transformer: 0.5.1 reflect-metadata: 0.2.2 - '@cucumber/pretty-formatter@1.0.1(@cucumber/cucumber@12.8.0)(@cucumber/messages@32.2.0)': + '@cucumber/pretty-formatter@1.0.1(@cucumber/cucumber@12.8.1)(@cucumber/messages@32.2.0)': dependencies: - '@cucumber/cucumber': 12.8.0 + '@cucumber/cucumber': 12.8.1 '@cucumber/messages': 32.2.0 ansi-styles: 5.2.0 cli-table3: 0.6.5 figures: 3.2.0 ts-dedent: 2.2.0 - '@cucumber/query@14.7.0(@cucumber/messages@32.2.0)': + '@cucumber/query@15.0.1(@cucumber/messages@32.2.0)': dependencies: '@cucumber/messages': 32.2.0 '@teppeis/multimaps': 3.0.0 @@ -8857,29 +8968,45 @@ snapshots: '@cucumber/tag-expressions@9.1.0': {} - '@e18e/eslint-plugin@0.3.0(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))': + '@e18e/eslint-plugin@0.3.0(eslint@10.2.1(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))': dependencies: - eslint-plugin-depend: 1.5.0(eslint@10.2.0(jiti@2.6.1)) + eslint-plugin-depend: 1.5.0(eslint@10.2.1(jiti@2.6.1)) optionalDependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) oxlint: 1.60.0(oxlint-tsgolint@0.21.1) - '@egoist/tailwindcss-icons@1.9.2(tailwindcss@4.2.2)': + '@egoist/tailwindcss-icons@1.9.2(tailwindcss@4.2.4)': dependencies: '@iconify/utils': 3.1.0 - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true '@emnapi/runtime@1.9.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emoji-mart/data@1.2.1': {} '@es-joy/jsdoccomment@0.84.0': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/types': 8.59.0 comment-parser: 1.4.5 esquery: 1.7.0 jsdoc-type-pratt-parser: 7.1.1 @@ -8887,7 +9014,7 @@ snapshots: '@es-joy/jsdoccomment@0.86.0': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/types': 8.59.0 comment-parser: 1.4.6 esquery: 1.7.0 jsdoc-type-pratt-parser: 7.2.0 @@ -8972,15 +9099,15 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.2.0(jiti@2.6.1))': + '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.2.1(jiti@2.6.1))': dependencies: escape-string-regexp: 4.0.0 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) ignore: 7.0.5 - '@eslint-community/eslint-utils@4.9.1(eslint@10.2.0(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1(jiti@2.6.1))': dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/eslint-utils@4.9.1(eslint@9.27.0(jiti@2.6.1))': @@ -8990,77 +9117,77 @@ snapshots: '@eslint-community/regexpp@4.12.2': {} - '@eslint-react/ast@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@eslint-react/ast@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) string-ts: 2.3.1 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/core@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@eslint-react/core@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/var': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/ast': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/eslint-plugin@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@eslint-react/eslint-plugin@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) - eslint-plugin-react-dom: 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-react-naming-convention: 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-react-rsc: 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-react-web-api: 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint-plugin-react-x: 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) + eslint-plugin-react-dom: 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-naming-convention: 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-rsc: 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-web-api: 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint-plugin-react-x: 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint-react/shared@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@eslint-react/shared@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 zod: 4.3.6 transitivePeerDependencies: - supports-color - '@eslint-react/var@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@eslint-react/var@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/ast': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@eslint/compat@2.0.3(eslint@10.2.0(jiti@2.6.1))': + '@eslint/compat@2.0.3(eslint@10.2.1(jiti@2.6.1))': dependencies: '@eslint/core': 1.2.0 optionalDependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) '@eslint/config-array@0.20.1': dependencies: @@ -9070,9 +9197,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-array@0.23.4': + '@eslint/config-array@0.23.5': dependencies: - '@eslint/object-schema': 3.0.4 + '@eslint/object-schema': 3.0.5 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.4 transitivePeerDependencies: @@ -9084,6 +9211,10 @@ snapshots: dependencies: '@eslint/core': 1.2.0 + '@eslint/config-helpers@0.5.5': + dependencies: + '@eslint/core': 1.2.1 + '@eslint/core@0.14.0': dependencies: '@types/json-schema': 7.0.15 @@ -9100,6 +9231,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/css-tree@4.0.1': dependencies: mdn-data: 2.27.1 @@ -9119,9 +9254,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@10.0.1(eslint@10.2.0(jiti@2.6.1))': + '@eslint/js@10.0.1(eslint@10.2.1(jiti@2.6.1))': optionalDependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) '@eslint/js@9.27.0': {} @@ -9157,7 +9292,7 @@ snapshots: '@eslint/object-schema@2.1.7': {} - '@eslint/object-schema@3.0.4': {} + '@eslint/object-schema@3.0.5': {} '@eslint/plugin-kit@0.3.5': dependencies: @@ -9174,9 +9309,9 @@ snapshots: '@eslint/core': 1.2.0 levn: 0.4.1 - '@eslint/plugin-kit@0.7.0': + '@eslint/plugin-kit@0.7.1': dependencies: - '@eslint/core': 1.2.0 + '@eslint/core': 1.2.1 levn: 0.4.1 '@floating-ui/core@1.7.5': @@ -9223,7 +9358,7 @@ snapshots: '@floating-ui/react': 0.26.28(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@react-aria/focus': 3.21.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@react-aria/interactions': 3.27.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@tanstack/react-virtual': 3.13.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/react-virtual': 3.13.24(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 react-dom: 19.2.5(react@19.2.5) use-sync-external-store: 1.6.0(react@19.2.5) @@ -9390,13 +9525,13 @@ snapshots: dependencies: minipass: 7.1.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3)': dependencies: glob: 13.0.6 - react-docgen-typescript: 2.4.0(typescript@6.0.2) - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + react-docgen-typescript: 2.4.0(typescript@6.0.3) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -9639,9 +9774,17 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - '@napi-rs/wasm-runtime@1.1.2(@emnapi/runtime@1.9.1)': + '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: - '@emnapi/runtime': 1.9.1 + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 '@tybys/wasm-util': 0.10.1 optional: true @@ -9649,41 +9792,41 @@ snapshots: '@next/env@16.0.0': {} - '@next/env@16.2.3': {} + '@next/env@16.2.4': {} - '@next/eslint-plugin-next@16.2.3': + '@next/eslint-plugin-next@16.2.4': dependencies: fast-glob: 3.3.1 - '@next/mdx@16.2.3(@mdx-js/loader@3.1.1)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))': + '@next/mdx@16.2.4(@mdx-js/loader@3.1.1)(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5))': dependencies: source-map: 0.7.6 optionalDependencies: '@mdx-js/loader': 3.1.1 '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) - '@next/swc-darwin-arm64@16.2.3': + '@next/swc-darwin-arm64@16.2.4': optional: true - '@next/swc-darwin-x64@16.2.3': + '@next/swc-darwin-x64@16.2.4': optional: true - '@next/swc-linux-arm64-gnu@16.2.3': + '@next/swc-linux-arm64-gnu@16.2.4': optional: true - '@next/swc-linux-arm64-musl@16.2.3': + '@next/swc-linux-arm64-musl@16.2.4': optional: true - '@next/swc-linux-x64-gnu@16.2.3': + '@next/swc-linux-x64-gnu@16.2.4': optional: true - '@next/swc-linux-x64-musl@16.2.3': + '@next/swc-linux-x64-musl@16.2.4': optional: true - '@next/swc-win32-arm64-msvc@16.2.3': + '@next/swc-win32-arm64-msvc@16.2.4': optional: true - '@next/swc-win32-x64-msvc@16.2.3': + '@next/swc-win32-x64-msvc@16.2.4': optional: true '@nodelib/fs.scandir@2.1.5': @@ -9756,85 +9899,82 @@ snapshots: transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/tanstack-query@1.13.14(@orpc/client@1.13.14)(@tanstack/query-core@5.99.0)': + '@orpc/tanstack-query@1.13.14(@orpc/client@1.13.14)(@tanstack/query-core@5.99.2)': dependencies: '@orpc/client': 1.13.14 '@orpc/shared': 1.13.14 - '@tanstack/query-core': 5.99.0 + '@tanstack/query-core': 5.99.2 transitivePeerDependencies: - '@opentelemetry/api' '@ota-meshi/ast-token-store@0.3.0': {} - '@oxc-parser/binding-android-arm-eabi@0.121.0': + '@oxc-parser/binding-android-arm-eabi@0.126.0': optional: true - '@oxc-parser/binding-android-arm64@0.121.0': + '@oxc-parser/binding-android-arm64@0.126.0': optional: true - '@oxc-parser/binding-darwin-arm64@0.121.0': + '@oxc-parser/binding-darwin-arm64@0.126.0': optional: true - '@oxc-parser/binding-darwin-x64@0.121.0': + '@oxc-parser/binding-darwin-x64@0.126.0': optional: true - '@oxc-parser/binding-freebsd-x64@0.121.0': + '@oxc-parser/binding-freebsd-x64@0.126.0': optional: true - '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0': + '@oxc-parser/binding-linux-arm-gnueabihf@0.126.0': optional: true - '@oxc-parser/binding-linux-arm-musleabihf@0.121.0': + '@oxc-parser/binding-linux-arm-musleabihf@0.126.0': optional: true - '@oxc-parser/binding-linux-arm64-gnu@0.121.0': + '@oxc-parser/binding-linux-arm64-gnu@0.126.0': optional: true - '@oxc-parser/binding-linux-arm64-musl@0.121.0': + '@oxc-parser/binding-linux-arm64-musl@0.126.0': optional: true - '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': + '@oxc-parser/binding-linux-ppc64-gnu@0.126.0': optional: true - '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': + '@oxc-parser/binding-linux-riscv64-gnu@0.126.0': optional: true - '@oxc-parser/binding-linux-riscv64-musl@0.121.0': + '@oxc-parser/binding-linux-riscv64-musl@0.126.0': optional: true - '@oxc-parser/binding-linux-s390x-gnu@0.121.0': + '@oxc-parser/binding-linux-s390x-gnu@0.126.0': optional: true - '@oxc-parser/binding-linux-x64-gnu@0.121.0': + '@oxc-parser/binding-linux-x64-gnu@0.126.0': optional: true - '@oxc-parser/binding-linux-x64-musl@0.121.0': + '@oxc-parser/binding-linux-x64-musl@0.126.0': optional: true - '@oxc-parser/binding-openharmony-arm64@0.121.0': + '@oxc-parser/binding-openharmony-arm64@0.126.0': optional: true - '@oxc-parser/binding-wasm32-wasi@0.121.0(@emnapi/runtime@1.9.1)': + '@oxc-parser/binding-wasm32-wasi@0.126.0': dependencies: - '@napi-rs/wasm-runtime': 1.1.2(@emnapi/runtime@1.9.1) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true - '@oxc-parser/binding-win32-arm64-msvc@0.121.0': + '@oxc-parser/binding-win32-arm64-msvc@0.126.0': optional: true - '@oxc-parser/binding-win32-ia32-msvc@0.121.0': + '@oxc-parser/binding-win32-ia32-msvc@0.126.0': optional: true - '@oxc-parser/binding-win32-x64-msvc@0.121.0': + '@oxc-parser/binding-win32-x64-msvc@0.126.0': optional: true '@oxc-project/runtime@0.126.0': {} - '@oxc-project/types@0.121.0': {} - '@oxc-project/types@0.126.0': {} '@oxc-resolver/binding-android-arm-eabi@11.19.1': @@ -9885,9 +10025,9 @@ snapshots: '@oxc-resolver/binding-openharmony-arm64@11.19.1': optional: true - '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/runtime@1.9.1)': + '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: - '@napi-rs/wasm-runtime': 1.1.2(@emnapi/runtime@1.9.1) + '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -10353,38 +10493,38 @@ snapshots: estree-walker: 2.0.2 picomatch: 4.0.4 - '@sentry-internal/browser-utils@10.48.0': + '@sentry-internal/browser-utils@10.49.0': dependencies: - '@sentry/core': 10.48.0 + '@sentry/core': 10.49.0 - '@sentry-internal/feedback@10.48.0': + '@sentry-internal/feedback@10.49.0': dependencies: - '@sentry/core': 10.48.0 + '@sentry/core': 10.49.0 - '@sentry-internal/replay-canvas@10.48.0': + '@sentry-internal/replay-canvas@10.49.0': dependencies: - '@sentry-internal/replay': 10.48.0 - '@sentry/core': 10.48.0 + '@sentry-internal/replay': 10.49.0 + '@sentry/core': 10.49.0 - '@sentry-internal/replay@10.48.0': + '@sentry-internal/replay@10.49.0': dependencies: - '@sentry-internal/browser-utils': 10.48.0 - '@sentry/core': 10.48.0 + '@sentry-internal/browser-utils': 10.49.0 + '@sentry/core': 10.49.0 - '@sentry/browser@10.48.0': + '@sentry/browser@10.49.0': dependencies: - '@sentry-internal/browser-utils': 10.48.0 - '@sentry-internal/feedback': 10.48.0 - '@sentry-internal/replay': 10.48.0 - '@sentry-internal/replay-canvas': 10.48.0 - '@sentry/core': 10.48.0 + '@sentry-internal/browser-utils': 10.49.0 + '@sentry-internal/feedback': 10.49.0 + '@sentry-internal/replay': 10.49.0 + '@sentry-internal/replay-canvas': 10.49.0 + '@sentry/core': 10.49.0 - '@sentry/core@10.48.0': {} + '@sentry/core@10.49.0': {} - '@sentry/react@10.48.0(react@19.2.5)': + '@sentry/react@10.49.0(react@19.2.5)': dependencies: - '@sentry/browser': 10.48.0 - '@sentry/core': 10.48.0 + '@sentry/browser': 10.49.0 + '@sentry/core': 10.49.0 react: 19.2.5 '@shikijs/core@4.0.2': @@ -10474,10 +10614,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) - '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) react: 19.2.5 @@ -10507,24 +10647,24 @@ snapshots: storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/builder-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: - '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/csf-plugin': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/csf-plugin@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' '@storybook/global@5.0.0': {} @@ -10533,20 +10673,20 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - '@storybook/nextjs-vite@10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2)': + '@storybook/nextjs-vite@10.3.5(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)': dependencies: - '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) - '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) - '@storybook/react-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) - next: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) + '@storybook/react-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) + next: 16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 react-dom: 19.2.5(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5) - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - vite-plugin-storybook-nextjs: 3.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' + vite-plugin-storybook-nextjs: 3.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -10561,12 +10701,12 @@ snapshots: react-dom: 19.2.5(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@storybook/react-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2)': + '@storybook/react-vite@10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3) '@rollup/pluginutils': 5.3.0 - '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) - '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2) + '@storybook/builder-vite': 10.3.5(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) empathic: 2.0.0 magic-string: 0.30.21 react: 19.2.5 @@ -10575,7 +10715,7 @@ snapshots: resolve: 1.22.11 storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tsconfig-paths: 4.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' transitivePeerDependencies: - esbuild - rollup @@ -10583,17 +10723,17 @@ snapshots: - typescript - webpack - '@storybook/react@10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2)': + '@storybook/react@10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)': dependencies: '@storybook/global': 5.0.0 '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) react: 19.2.5 react-docgen: 8.0.3 - react-docgen-typescript: 2.4.0(typescript@6.0.2) + react-docgen-typescript: 2.4.0(typescript@6.0.3) react-dom: 19.2.5(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -10606,11 +10746,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.2.1(jiti@2.6.1))': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) - '@typescript-eslint/types': 8.58.2 - eslint: 10.2.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@typescript-eslint/types': 8.59.0 + eslint: 10.2.1(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -10626,21 +10766,21 @@ snapshots: dependencies: tslib: 2.8.1 - '@t3-oss/env-core@0.13.11(typescript@6.0.2)(valibot@1.3.1(typescript@6.0.2))(zod@4.3.6)': + '@t3-oss/env-core@0.13.11(typescript@6.0.3)(valibot@1.3.1(typescript@6.0.3))(zod@4.3.6)': optionalDependencies: - typescript: 6.0.2 - valibot: 1.3.1(typescript@6.0.2) + typescript: 6.0.3 + valibot: 1.3.1(typescript@6.0.3) zod: 4.3.6 - '@t3-oss/env-nextjs@0.13.11(typescript@6.0.2)(valibot@1.3.1(typescript@6.0.2))(zod@4.3.6)': + '@t3-oss/env-nextjs@0.13.11(typescript@6.0.3)(valibot@1.3.1(typescript@6.0.3))(zod@4.3.6)': dependencies: - '@t3-oss/env-core': 0.13.11(typescript@6.0.2)(valibot@1.3.1(typescript@6.0.2))(zod@4.3.6) + '@t3-oss/env-core': 0.13.11(typescript@6.0.3)(valibot@1.3.1(typescript@6.0.3))(zod@4.3.6) optionalDependencies: - typescript: 6.0.2 - valibot: 1.3.1(typescript@6.0.2) + typescript: 6.0.3 + valibot: 1.3.1(typescript@6.0.3) zod: 4.3.6 - '@tailwindcss/node@4.2.2': + '@tailwindcss/node@4.2.4': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.20.1 @@ -10648,78 +10788,78 @@ snapshots: lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 - '@tailwindcss/oxide-android-arm64@4.2.2': + '@tailwindcss/oxide-android-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.2': + '@tailwindcss/oxide-darwin-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.2': + '@tailwindcss/oxide-darwin-x64@4.2.4': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.2': + '@tailwindcss/oxide-freebsd-x64@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.2': + '@tailwindcss/oxide-linux-x64-musl@4.2.4': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.2': + '@tailwindcss/oxide-wasm32-wasi@4.2.4': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': optional: true - '@tailwindcss/oxide@4.2.2': + '@tailwindcss/oxide@4.2.4': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-x64': 4.2.2 - '@tailwindcss/oxide-freebsd-x64': 4.2.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-x64-musl': 4.2.2 - '@tailwindcss/oxide-wasm32-wasi': 4.2.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 - '@tailwindcss/postcss@4.2.2': + '@tailwindcss/postcss@4.2.4': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - postcss: 8.5.9 - tailwindcss: 4.2.2 + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + postcss: 8.5.10 + tailwindcss: 4.2.4 - '@tailwindcss/typography@0.5.19(tailwindcss@4.2.2)': + '@tailwindcss/typography@0.5.19(tailwindcss@4.2.4)': dependencies: postcss-selector-parser: 6.0.10 - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 - '@tailwindcss/vite@4.2.2(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))': dependencies: - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - tailwindcss: 4.2.2 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' '@tanstack/devtools-client@0.0.6': dependencies: @@ -10765,26 +10905,26 @@ snapshots: - csstype - utf-8-validate - '@tanstack/eslint-plugin-query@5.99.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@tanstack/eslint-plugin-query@5.99.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@tanstack/form-core@1.29.0': + '@tanstack/form-core@1.29.1': dependencies: '@tanstack/devtools-event-client': 0.4.3 '@tanstack/pacer-lite': 0.1.1 '@tanstack/store': 0.9.3 - '@tanstack/form-devtools@0.2.21(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11)': + '@tanstack/form-devtools@0.2.22(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11)': dependencies: '@tanstack/devtools-ui': 0.5.1(csstype@3.2.3) '@tanstack/devtools-utils': 0.4.0(@types/react@19.2.14)(react@19.2.5)(solid-js@1.9.11) - '@tanstack/form-core': 1.29.0 + '@tanstack/form-core': 1.29.1 clsx: 2.1.1 dayjs: 1.11.20 goober: 2.1.18(csstype@3.2.3) @@ -10798,9 +10938,9 @@ snapshots: '@tanstack/pacer-lite@0.1.1': {} - '@tanstack/query-core@5.99.0': {} + '@tanstack/query-core@5.99.2': {} - '@tanstack/query-devtools@5.99.0': {} + '@tanstack/query-devtools@5.99.2': {} '@tanstack/react-devtools@0.10.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: @@ -10814,10 +10954,10 @@ snapshots: - csstype - utf-8-validate - '@tanstack/react-form-devtools@0.2.21(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11)': + '@tanstack/react-form-devtools@0.2.22(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11)': dependencies: '@tanstack/devtools-utils': 0.4.0(@types/react@19.2.14)(react@19.2.5)(solid-js@1.9.11) - '@tanstack/form-devtools': 0.2.21(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11) + '@tanstack/form-devtools': 0.2.22(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.5)(solid-js@1.9.11) react: 19.2.5 transitivePeerDependencies: - '@types/react' @@ -10826,23 +10966,23 @@ snapshots: - solid-js - vue - '@tanstack/react-form@1.29.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@tanstack/react-form@1.29.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@tanstack/form-core': 1.29.0 + '@tanstack/form-core': 1.29.1 '@tanstack/react-store': 0.9.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react: 19.2.5 transitivePeerDependencies: - react-dom - '@tanstack/react-query-devtools@5.99.0(@tanstack/react-query@5.99.0(react@19.2.5))(react@19.2.5)': + '@tanstack/react-query-devtools@5.99.2(@tanstack/react-query@5.99.2(react@19.2.5))(react@19.2.5)': dependencies: - '@tanstack/query-devtools': 5.99.0 - '@tanstack/react-query': 5.99.0(react@19.2.5) + '@tanstack/query-devtools': 5.99.2 + '@tanstack/react-query': 5.99.2(react@19.2.5) react: 19.2.5 - '@tanstack/react-query@5.99.0(react@19.2.5)': + '@tanstack/react-query@5.99.2(react@19.2.5)': dependencies: - '@tanstack/query-core': 5.99.0 + '@tanstack/query-core': 5.99.2 react: 19.2.5 '@tanstack/react-store@0.9.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': @@ -10852,15 +10992,15 @@ snapshots: react-dom: 19.2.5(react@19.2.5) use-sync-external-store: 1.6.0(react@19.2.5) - '@tanstack/react-virtual@3.13.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@tanstack/react-virtual@3.13.24(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@tanstack/virtual-core': 3.13.23 + '@tanstack/virtual-core': 3.14.0 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) '@tanstack/store@0.9.3': {} - '@tanstack/virtual-core@3.13.23': {} + '@tanstack/virtual-core@3.14.0': {} '@teppeis/multimaps@3.0.0': {} @@ -10898,46 +11038,46 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 - '@tsslint/cli@3.0.3(@tsslint/compat-eslint@3.0.3(jiti@2.6.1)(typescript@6.0.2))(typescript@6.0.2)': + '@tsslint/cli@3.0.4(@tsslint/compat-eslint@3.0.4(jiti@2.6.1)(typescript@6.0.3))(typescript@6.0.3)': dependencies: '@clack/prompts': 0.8.2 - '@tsslint/config': 3.0.3(@tsslint/compat-eslint@3.0.3(jiti@2.6.1)(typescript@6.0.2))(typescript@6.0.2) - '@tsslint/core': 3.0.3 + '@tsslint/config': 3.0.4(@tsslint/compat-eslint@3.0.4(jiti@2.6.1)(typescript@6.0.3))(typescript@6.0.3) + '@tsslint/core': 3.0.4 '@volar/language-core': 2.4.28 '@volar/language-hub': 0.0.1 '@volar/typescript': 2.4.28 minimatch: 10.2.4 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - '@tsslint/compat-eslint' - tsl - '@tsslint/compat-eslint@3.0.3(jiti@2.6.1)(typescript@6.0.2)': + '@tsslint/compat-eslint@3.0.4(jiti@2.6.1)(typescript@6.0.3)': dependencies: - '@tsslint/types': 3.0.3 - '@typescript-eslint/parser': 8.58.2(eslint@9.27.0(jiti@2.6.1))(typescript@6.0.2) + '@tsslint/types': 3.0.4 + '@typescript-eslint/parser': 8.59.0(eslint@9.27.0(jiti@2.6.1))(typescript@6.0.3) eslint: 9.27.0(jiti@2.6.1) transitivePeerDependencies: - jiti - supports-color - typescript - '@tsslint/config@3.0.3(@tsslint/compat-eslint@3.0.3(jiti@2.6.1)(typescript@6.0.2))(typescript@6.0.2)': + '@tsslint/config@3.0.4(@tsslint/compat-eslint@3.0.4(jiti@2.6.1)(typescript@6.0.3))(typescript@6.0.3)': dependencies: - '@tsslint/types': 3.0.3 + '@tsslint/types': 3.0.4 minimatch: 10.2.4 - ts-api-utils: 2.5.0(typescript@6.0.2) + ts-api-utils: 2.5.0(typescript@6.0.3) optionalDependencies: - '@tsslint/compat-eslint': 3.0.3(jiti@2.6.1)(typescript@6.0.2) + '@tsslint/compat-eslint': 3.0.4(jiti@2.6.1)(typescript@6.0.3) transitivePeerDependencies: - typescript - '@tsslint/core@3.0.3': + '@tsslint/core@3.0.4': dependencies: - '@tsslint/types': 3.0.3 + '@tsslint/types': 3.0.4 minimatch: 10.2.4 - '@tsslint/types@3.0.3': {} + '@tsslint/types@3.0.4': {} '@tybys/wasm-util@0.10.1': dependencies: @@ -11175,52 +11315,89 @@ snapshots: '@types/zen-observable@0.8.3': {} - '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.58.2 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 + eslint: 10.2.1(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.2.0(jiti@2.6.1) - typescript: 6.0.2 + eslint: 10.2.1(jiti@2.6.1) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.2(eslint@9.27.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.58.2 + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.2.1(jiti@2.6.1) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.59.0(eslint@9.27.0(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3(supports-color@8.1.1) eslint: 9.27.0(jiti@2.6.1) - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.58.2(typescript@6.0.2)': + '@typescript-eslint/project-service@8.58.2(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.3) '@typescript-eslint/types': 8.58.2 debug: 4.4.3(supports-color@8.1.1) - typescript: 6.0.2 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.59.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + debug: 4.4.3(supports-color@8.1.1) + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -11229,47 +11406,96 @@ snapshots: '@typescript-eslint/types': 8.58.2 '@typescript-eslint/visitor-keys': 8.58.2 - '@typescript-eslint/tsconfig-utils@8.58.2(typescript@6.0.2)': + '@typescript-eslint/scope-manager@8.59.0': dependencies: - typescript: 6.0.2 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 - '@typescript-eslint/type-utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/tsconfig-utils@8.59.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.2.0(jiti@2.6.1) - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + eslint: 10.2.1(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.2.1(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@8.58.2': {} - '@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2)': + '@typescript-eslint/types@8.59.0': {} + + '@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.58.2(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/project-service': 8.58.2(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.3) '@typescript-eslint/types': 8.58.2 '@typescript-eslint/visitor-keys': 8.58.2 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.4 semver: 7.7.4 tinyglobby: 0.2.15 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': + '@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@typescript-eslint/project-service': 8.59.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 + debug: 4.4.3(supports-color@8.1.1) + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) - typescript: 6.0.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -11278,36 +11504,41 @@ snapshots: '@typescript-eslint/types': 8.58.2 eslint-visitor-keys: 5.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260413.1': + '@typescript-eslint/visitor-keys@8.59.0': + dependencies: + '@typescript-eslint/types': 8.59.0 + eslint-visitor-keys: 5.0.1 + + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260422.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260413.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260422.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260413.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260422.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260413.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260422.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260413.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260422.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260413.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260422.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260413.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260422.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260413.1': + '@typescript/native-preview@7.0.0-dev.20260422.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260413.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260413.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260413.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260413.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260413.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260413.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260413.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260422.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260422.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260422.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260422.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260422.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260422.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260422.1 '@ungap/structured-clone@1.3.0': {} @@ -11315,56 +11546,56 @@ snapshots: dependencies: unpic: 4.2.2 - '@unpic/react@1.0.2(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@unpic/react@1.0.2(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@unpic/core': 1.0.3 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) optionalDependencies: - next: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@upsetjs/venn.js@2.0.0': optionalDependencies: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@valibot/to-json-schema@1.6.0(valibot@1.3.1(typescript@6.0.2))': + '@valibot/to-json-schema@1.6.0(valibot@1.3.1(typescript@6.0.3))': dependencies: - valibot: 1.3.1(typescript@6.0.2) + valibot: 1.3.1(typescript@6.0.3) '@vercel/og@0.8.6': dependencies: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vitejs/devtools-kit@0.1.11(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0)': + '@vitejs/devtools-kit@0.1.11(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3)(ws@8.20.0)': dependencies: - '@vitejs/devtools-rpc': 0.1.11(typescript@6.0.2)(ws@8.20.0) + '@vitejs/devtools-rpc': 0.1.11(typescript@6.0.3)(ws@8.20.0) birpc: 4.0.0 ohash: 2.0.11 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' transitivePeerDependencies: - typescript - ws - '@vitejs/devtools-rpc@0.1.11(typescript@6.0.2)(ws@8.20.0)': + '@vitejs/devtools-rpc@0.1.11(typescript@6.0.3)(ws@8.20.0)': dependencies: birpc: 4.0.0 ohash: 2.0.11 p-limit: 7.3.0 structured-clone-es: 2.0.0 - valibot: 1.3.1(typescript@6.0.2) + valibot: 1.3.1(typescript@6.0.3) optionalDependencies: ws: 8.20.0 transitivePeerDependencies: - typescript - '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))': + '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' - '@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)': + '@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)': dependencies: '@rolldown/pluginutils': 1.0.0-rc.15 es-module-lexer: 2.0.0 @@ -11375,15 +11606,15 @@ snapshots: srvx: 0.11.15 strip-literal: 3.1.0 turbo-stream: 3.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - vitefu: 1.1.3(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' + vitefu: 1.1.3(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)) optionalDependencies: react-server-dom-webpack: 19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.5 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -11392,7 +11623,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -11422,15 +11653,15 @@ snapshots: - vite - yaml - '@vitest/eslint-plugin@1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.0(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@vitest/eslint-plugin@1.6.15(@types/node@25.6.0)(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(eslint@10.2.1(jiti@2.6.1))(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) - vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - typescript: 6.0.2 + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -11473,7 +11704,7 @@ snapshots: dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.1.4': + '@vitest/pretty-format@4.1.5': dependencies: tinyrainbow: 3.1.0 @@ -11487,13 +11718,13 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 - '@vitest/utils@4.1.4': + '@vitest/utils@4.1.5': dependencies: - '@vitest/pretty-format': 4.1.4 + '@vitest/pretty-format': 4.1.5 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)': dependencies: '@oxc-project/runtime': 0.126.0 '@oxc-project/types': 0.126.0 @@ -11505,7 +11736,7 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 tsx: 4.21.0 - typescript: 6.0.2 + typescript: 6.0.3 yaml: 2.8.3 '@voidzero-dev/vite-plus-darwin-arm64@0.1.19': @@ -11526,11 +11757,11 @@ snapshots: '@voidzero-dev/vite-plus-linux-x64-musl@0.1.19': optional: true - '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)': + '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@voidzero-dev/vite-plus-core': 0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) es-module-lexer: 1.7.0 obug: 2.1.1 pixelmatch: 7.1.0 @@ -11540,11 +11771,11 @@ snapshots: tinybench: 2.9.0 tinyexec: 1.0.4 tinyglobby: 0.2.16 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' ws: 8.20.0 optionalDependencies: '@types/node': 25.6.0 - '@vitest/coverage-v8': 4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@vitest/coverage-v8': 4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) happy-dom: 20.9.0 transitivePeerDependencies: - '@arethetypeswrong/core' @@ -11774,7 +12005,7 @@ snapshots: caniuse-lite@1.0.30001781: {} - canvas@3.2.2: + canvas@3.2.3: dependencies: node-addon-api: 7.1.1 prebuild-install: 7.1.3 @@ -12279,7 +12510,7 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 - dompurify@3.4.0: + dompurify@3.4.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -12373,7 +12604,7 @@ snapshots: es-module-lexer@2.0.0: {} - es-toolkit@1.45.1: {} + es-toolkit@1.46.0: {} esast-util-from-estree@2.0.0: dependencies: @@ -12428,93 +12659,93 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@10.2.0(jiti@2.6.1)): + eslint-compat-utils@0.5.1(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) semver: 7.7.4 - eslint-config-flat-gitignore@2.3.0(eslint@10.2.0(jiti@2.6.1)): + eslint-config-flat-gitignore@2.3.0(eslint@10.2.1(jiti@2.6.1)): dependencies: - '@eslint/compat': 2.0.3(eslint@10.2.0(jiti@2.6.1)) - eslint: 10.2.0(jiti@2.6.1) + '@eslint/compat': 2.0.3(eslint@10.2.1(jiti@2.6.1)) + eslint: 10.2.1(jiti@2.6.1) eslint-flat-config-utils@3.1.0: dependencies: '@eslint/config-helpers': 0.5.4 pathe: 2.0.3 - eslint-json-compat-utils@0.2.3(eslint@10.2.0(jiti@2.6.1))(jsonc-eslint-parser@3.1.0): + eslint-json-compat-utils@0.2.3(eslint@10.2.1(jiti@2.6.1))(jsonc-eslint-parser@3.1.0): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) esquery: 1.7.0 jsonc-eslint-parser: 3.1.0 - eslint-markdown@0.6.1(eslint@10.2.0(jiti@2.6.1)): + eslint-markdown@0.6.1(eslint@10.2.1(jiti@2.6.1)): dependencies: '@eslint/markdown': 7.5.1 micromark-util-normalize-identifier: 2.0.1 parse5: 8.0.0 optionalDependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) transitivePeerDependencies: - supports-color - eslint-merge-processors@2.0.0(eslint@10.2.0(jiti@2.6.1)): + eslint-merge-processors@2.0.0(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) - eslint-plugin-antfu@3.2.2(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-antfu@3.2.2(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) - eslint-plugin-better-tailwindcss@4.4.1(eslint@10.2.0(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tailwindcss@4.2.2)(typescript@6.0.2): + eslint-plugin-better-tailwindcss@4.4.1(eslint@10.2.1(jiti@2.6.1))(oxlint@1.60.0(oxlint-tsgolint@0.21.1))(tailwindcss@4.2.4)(typescript@6.0.3): dependencies: '@eslint/css-tree': 4.0.1 - '@valibot/to-json-schema': 1.6.0(valibot@1.3.1(typescript@6.0.2)) + '@valibot/to-json-schema': 1.6.0(valibot@1.3.1(typescript@6.0.3)) enhanced-resolve: 5.20.1 jiti: 2.6.1 synckit: 0.11.12 tailwind-csstree: 0.3.1 - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 tsconfig-paths-webpack-plugin: 4.2.0 - valibot: 1.3.1(typescript@6.0.2) + valibot: 1.3.1(typescript@6.0.3) optionalDependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) oxlint: 1.60.0(oxlint-tsgolint@0.21.1) transitivePeerDependencies: - '@eslint/css' - typescript - eslint-plugin-command@3.5.2(@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2))(@typescript-eslint/utils@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-command@3.5.2(@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3))(@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1)): dependencies: '@es-joy/jsdoccomment': 0.84.0 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) - eslint-plugin-depend@1.5.0(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-depend@1.5.0(eslint@10.2.1(jiti@2.6.1)): dependencies: empathic: 2.0.0 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) module-replacements: 2.11.0 semver: 7.7.4 - eslint-plugin-es-x@7.8.0(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-es-x@7.8.0(eslint@10.2.1(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - eslint: 10.2.0(jiti@2.6.1) - eslint-compat-utils: 0.5.1(eslint@10.2.0(jiti@2.6.1)) + eslint: 10.2.1(jiti@2.6.1) + eslint-compat-utils: 0.5.1(eslint@10.2.1(jiti@2.6.1)) - eslint-plugin-hyoban@0.14.1(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-hyoban@0.14.1(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) - eslint-plugin-import-lite@0.6.0(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-import-lite@0.6.0(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) - eslint-plugin-jsdoc@62.9.0(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-jsdoc@62.9.0(eslint@10.2.1(jiti@2.6.1)): dependencies: '@es-joy/jsdoccomment': 0.86.0 '@es-joy/resolve.exports': 1.2.0 @@ -12522,7 +12753,7 @@ snapshots: comment-parser: 1.4.6 debug: 4.4.3(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) espree: 11.2.0 esquery: 1.7.0 html-entities: 2.6.0 @@ -12534,27 +12765,27 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-jsonc@3.1.2(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-jsonc@3.1.2(eslint@10.2.1(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) '@eslint/core': 1.2.0 '@eslint/plugin-kit': 0.6.1 '@ota-meshi/ast-token-store': 0.3.0 diff-sequences: 29.6.3 - eslint: 10.2.0(jiti@2.6.1) - eslint-json-compat-utils: 0.2.3(eslint@10.2.0(jiti@2.6.1))(jsonc-eslint-parser@3.1.0) + eslint: 10.2.1(jiti@2.6.1) + eslint-json-compat-utils: 0.2.3(eslint@10.2.1(jiti@2.6.1))(jsonc-eslint-parser@3.1.0) jsonc-eslint-parser: 3.1.0 natural-compare: 1.4.0 synckit: 0.11.12 transitivePeerDependencies: - '@eslint/json' - eslint-plugin-markdown-preferences@0.41.1(@eslint/markdown@8.0.1)(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-markdown-preferences@0.41.1(@eslint/markdown@8.0.1)(eslint@10.2.1(jiti@2.6.1)): dependencies: '@eslint/markdown': 8.0.1 diff-sequences: 29.6.3 emoji-regex-xs: 2.0.1 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) mdast-util-from-markdown: 2.0.3 mdast-util-frontmatter: 2.0.1 mdast-util-gfm: 3.1.0 @@ -12569,44 +12800,44 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-n@17.24.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-n@17.24.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) enhanced-resolve: 5.20.1 - eslint: 10.2.0(jiti@2.6.1) - eslint-plugin-es-x: 7.8.0(eslint@10.2.0(jiti@2.6.1)) + eslint: 10.2.1(jiti@2.6.1) + eslint-plugin-es-x: 7.8.0(eslint@10.2.1(jiti@2.6.1)) get-tsconfig: 4.13.7 globals: 15.15.0 globrex: 0.1.2 ignore: 5.3.2 semver: 7.7.4 - ts-declaration-location: 1.0.7(typescript@6.0.2) + ts-declaration-location: 1.0.7(typescript@6.0.3) transitivePeerDependencies: - typescript - eslint-plugin-no-barrel-files@1.3.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-no-barrel-files@1.3.1(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) transitivePeerDependencies: - supports-color - typescript eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-perfectionist@5.8.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-perfectionist@5.8.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-pnpm@1.6.0(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-pnpm@1.6.0(eslint@10.2.1(jiti@2.6.1)): dependencies: empathic: 2.0.0 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) jsonc-eslint-parser: 3.1.0 pathe: 2.0.3 pnpm-workspace-yaml: 1.6.0 @@ -12614,150 +12845,150 @@ snapshots: yaml: 2.8.3 yaml-eslint-parser: 2.0.0 - eslint-plugin-react-dom@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-react-dom@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/core': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/var': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/ast': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) compare-versions: 6.1.1 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-naming-convention@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-react-naming-convention@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/core': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/var': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/ast': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) compare-versions: 6.1.1 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) string-ts: 2.3.1 ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.5.2(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-react-refresh@0.5.2(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) - eslint-plugin-react-rsc@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-react-rsc@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/var': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/ast': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-web-api@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-react-web-api@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/core': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/var': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/ast': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) birecord: 0.1.1 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-x@3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-plugin-react-x@3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@eslint-react/ast': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/core': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/shared': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@eslint-react/var': 3.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@eslint-react/ast': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/core': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/shared': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@eslint-react/var': 3.0.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) compare-versions: 6.1.1 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) string-ts: 2.3.1 - ts-api-utils: 2.5.0(typescript@6.0.2) + ts-api-utils: 2.5.0(typescript@6.0.3) ts-pattern: 5.9.0 - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-regexp@3.1.0(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-regexp@3.1.0(eslint@10.2.1(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.6 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) jsdoc-type-pratt-parser: 7.2.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-sonarjs@4.0.2(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-sonarjs@4.0.3(eslint@10.2.1(jiti@2.6.1)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) functional-red-black-tree: 1.0.1 globals: 17.5.0 jsx-ast-utils-x: 0.1.0 lodash.merge: 4.6.2 - minimatch: 10.2.4 + minimatch: 10.2.5 scslre: 0.3.0 semver: 7.7.4 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 - eslint-plugin-storybook@10.3.5(eslint@10.2.0(jiti@2.6.1))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2): + eslint-plugin-storybook@10.3.5(eslint@10.2.1(jiti@2.6.1))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-toml@1.3.1(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-toml@1.3.1(eslint@10.2.1(jiti@2.6.1)): dependencies: '@eslint/core': 1.2.0 '@eslint/plugin-kit': 0.6.1 '@ota-meshi/ast-token-store': 0.3.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) toml-eslint-parser: 1.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@64.0.0(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-unicorn@64.0.0(eslint@10.2.1(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) find-up-simple: 1.0.1 globals: 17.5.0 indent-string: 5.0.0 @@ -12769,27 +13000,27 @@ snapshots: semver: 7.7.4 strip-indent: 4.1.1 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.0(jiti@2.6.1)))(@typescript-eslint/parser@8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))): + eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.2.1(jiti@2.6.1)))(@typescript-eslint/parser@8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.1(jiti@2.6.1))): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) - eslint: 10.2.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + eslint: 10.2.1(jiti@2.6.1) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.1 semver: 7.7.4 - vue-eslint-parser: 10.4.0(eslint@10.2.0(jiti@2.6.1)) + vue-eslint-parser: 10.4.0(eslint@10.2.1(jiti@2.6.1)) xml-name-validator: 4.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@10.2.0(jiti@2.6.1)) - '@typescript-eslint/parser': 8.58.2(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.2.1(jiti@2.6.1)) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) - eslint-plugin-yml@3.3.1(eslint@10.2.0(jiti@2.6.1)): + eslint-plugin-yml@3.3.1(eslint@10.2.1(jiti@2.6.1)): dependencies: '@eslint/core': 1.2.0 '@eslint/plugin-kit': 0.6.1 @@ -12797,15 +13028,15 @@ snapshots: debug: 4.4.3(supports-color@8.1.1) diff-sequences: 29.6.3 escape-string-regexp: 5.0.0 - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) natural-compare: 1.4.0 yaml-eslint-parser: 2.0.0 transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@2.0.0(eslint@10.2.0(jiti@2.6.1)): + eslint-processor-vue-blocks@2.0.0(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) eslint-scope@8.4.0: dependencies: @@ -12825,14 +13056,14 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.2.0(jiti@2.6.1): + eslint@10.2.1(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.4 - '@eslint/config-helpers': 0.5.4 - '@eslint/core': 1.2.0 - '@eslint/plugin-kit': 0.7.0 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 @@ -13093,6 +13324,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + github-from-package@0.0.0: optional: true @@ -13330,11 +13565,11 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 - i18next@26.0.4(typescript@6.0.2): + i18next@26.0.6(typescript@6.0.3): dependencies: '@babel/runtime': 7.29.2 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 iconify-import-svg@0.2.0: dependencies: @@ -13518,20 +13753,19 @@ snapshots: khroma@2.1.0: {} - knip@6.4.1(@emnapi/runtime@1.9.1): + knip@6.6.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2): dependencies: - '@nodelib/fs.walk': 1.2.8 - fast-glob: 3.3.3 + fdir: 6.5.0(picomatch@4.0.4) formatly: 0.3.0 - get-tsconfig: 4.13.7 + get-tsconfig: 4.14.0 jiti: 2.6.1 minimist: 1.2.8 - oxc-parser: 0.121.0(@emnapi/runtime@1.9.1) - oxc-resolver: 11.19.1(@emnapi/runtime@1.9.1) - picocolors: 1.1.1 + oxc-parser: 0.126.0 + oxc-resolver: 11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) picomatch: 4.0.4 smol-toml: 1.6.1 strip-json-comments: 5.0.3 + tinyglobby: 0.2.16 unbash: 2.2.0 yaml: 2.8.3 zod: 4.3.6 @@ -13545,7 +13779,7 @@ snapshots: kolorist@1.8.0: {} - ky@2.0.0: {} + ky@2.0.2: {} lamejs@1.2.1: dependencies: @@ -13660,7 +13894,7 @@ snapshots: dependencies: js-tokens: 4.0.0 - loro-crdt@1.10.8: {} + loro-crdt@1.11.1: {} loupe@3.2.1: {} @@ -14246,6 +14480,10 @@ snapshots: dependencies: brace-expansion: 5.0.5 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.13 @@ -14308,9 +14546,9 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - '@next/env': 16.2.3 + '@next/env': 16.2.4 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.12 caniuse-lite: 1.0.30001781 @@ -14319,14 +14557,14 @@ snapshots: react-dom: 19.2.5(react@19.2.5) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.3 - '@next/swc-darwin-x64': 16.2.3 - '@next/swc-linux-arm64-gnu': 16.2.3 - '@next/swc-linux-arm64-musl': 16.2.3 - '@next/swc-linux-x64-gnu': 16.2.3 - '@next/swc-linux-x64-musl': 16.2.3 - '@next/swc-win32-arm64-msvc': 16.2.3 - '@next/swc-win32-x64-msvc': 16.2.3 + '@next/swc-darwin-arm64': 16.2.4 + '@next/swc-darwin-x64': 16.2.4 + '@next/swc-linux-arm64-gnu': 16.2.4 + '@next/swc-linux-arm64-musl': 16.2.4 + '@next/swc-linux-x64-gnu': 16.2.4 + '@next/swc-linux-x64-musl': 16.2.4 + '@next/swc-win32-arm64-msvc': 16.2.4 + '@next/swc-win32-x64-msvc': 16.2.4 '@playwright/test': 1.59.1 sharp: 0.34.5 transitivePeerDependencies: @@ -14360,12 +14598,12 @@ snapshots: dependencies: boolbase: 1.0.0 - nuqs@2.8.9(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): + nuqs@2.8.9(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.5 optionalDependencies: - next: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) object-assign@4.1.1: {} @@ -14414,35 +14652,32 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - oxc-parser@0.121.0(@emnapi/runtime@1.9.1): + oxc-parser@0.126.0: dependencies: - '@oxc-project/types': 0.121.0 + '@oxc-project/types': 0.126.0 optionalDependencies: - '@oxc-parser/binding-android-arm-eabi': 0.121.0 - '@oxc-parser/binding-android-arm64': 0.121.0 - '@oxc-parser/binding-darwin-arm64': 0.121.0 - '@oxc-parser/binding-darwin-x64': 0.121.0 - '@oxc-parser/binding-freebsd-x64': 0.121.0 - '@oxc-parser/binding-linux-arm-gnueabihf': 0.121.0 - '@oxc-parser/binding-linux-arm-musleabihf': 0.121.0 - '@oxc-parser/binding-linux-arm64-gnu': 0.121.0 - '@oxc-parser/binding-linux-arm64-musl': 0.121.0 - '@oxc-parser/binding-linux-ppc64-gnu': 0.121.0 - '@oxc-parser/binding-linux-riscv64-gnu': 0.121.0 - '@oxc-parser/binding-linux-riscv64-musl': 0.121.0 - '@oxc-parser/binding-linux-s390x-gnu': 0.121.0 - '@oxc-parser/binding-linux-x64-gnu': 0.121.0 - '@oxc-parser/binding-linux-x64-musl': 0.121.0 - '@oxc-parser/binding-openharmony-arm64': 0.121.0 - '@oxc-parser/binding-wasm32-wasi': 0.121.0(@emnapi/runtime@1.9.1) - '@oxc-parser/binding-win32-arm64-msvc': 0.121.0 - '@oxc-parser/binding-win32-ia32-msvc': 0.121.0 - '@oxc-parser/binding-win32-x64-msvc': 0.121.0 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@oxc-parser/binding-android-arm-eabi': 0.126.0 + '@oxc-parser/binding-android-arm64': 0.126.0 + '@oxc-parser/binding-darwin-arm64': 0.126.0 + '@oxc-parser/binding-darwin-x64': 0.126.0 + '@oxc-parser/binding-freebsd-x64': 0.126.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.126.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.126.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.126.0 + '@oxc-parser/binding-linux-arm64-musl': 0.126.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.126.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.126.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.126.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.126.0 + '@oxc-parser/binding-linux-x64-gnu': 0.126.0 + '@oxc-parser/binding-linux-x64-musl': 0.126.0 + '@oxc-parser/binding-openharmony-arm64': 0.126.0 + '@oxc-parser/binding-wasm32-wasi': 0.126.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.126.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.126.0 + '@oxc-parser/binding-win32-x64-msvc': 0.126.0 - oxc-resolver@11.19.1(@emnapi/runtime@1.9.1): + oxc-resolver@11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2): optionalDependencies: '@oxc-resolver/binding-android-arm-eabi': 11.19.1 '@oxc-resolver/binding-android-arm64': 11.19.1 @@ -14460,7 +14695,7 @@ snapshots: '@oxc-resolver/binding-linux-x64-gnu': 11.19.1 '@oxc-resolver/binding-linux-x64-musl': 11.19.1 '@oxc-resolver/binding-openharmony-arm64': 11.19.1 - '@oxc-resolver/binding-wasm32-wasi': 11.19.1(@emnapi/runtime@1.9.1) + '@oxc-resolver/binding-wasm32-wasi': 11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) '@oxc-resolver/binding-win32-arm64-msvc': 11.19.1 '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1 '@oxc-resolver/binding-win32-x64-msvc': 11.19.1 @@ -14620,7 +14855,7 @@ snapshots: pdfjs-dist@4.4.168: optionalDependencies: - canvas: 3.2.2 + canvas: 3.2.3 path2d: 0.2.2 pend@1.2.0: {} @@ -14699,6 +14934,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.9: dependencies: nanoid: 3.3.11 @@ -14782,9 +15023,9 @@ snapshots: prop-types: 15.8.1 react: 19.2.5 - react-docgen-typescript@2.4.0(typescript@6.0.2): + react-docgen-typescript@2.4.0(typescript@6.0.3): dependencies: - typescript: 6.0.2 + typescript: 6.0.3 react-docgen@8.0.3: dependencies: @@ -14831,16 +15072,16 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - react-i18next@16.5.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2): + react-i18next@16.5.8(i18next@26.0.6(typescript@6.0.3))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.3): dependencies: '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 26.0.4(typescript@6.0.2) + i18next: 26.0.6(typescript@6.0.3) react: 19.2.5 use-sync-external-store: 1.6.0(react@19.2.5) optionalDependencies: react-dom: 19.2.5(react@19.2.5) - typescript: 6.0.2 + typescript: 6.0.3 react-is@16.13.1: {} @@ -15493,7 +15734,7 @@ snapshots: tailwind-merge@3.5.0: {} - tailwindcss@4.2.2: {} + tailwindcss@4.2.4: {} tapable@2.3.2: {} @@ -15589,24 +15830,24 @@ snapshots: trough@2.2.0: {} - ts-api-utils@2.5.0(typescript@6.0.2): + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: - typescript: 6.0.2 + typescript: 6.0.3 ts-debounce@4.0.0: {} - ts-declaration-location@1.0.7(typescript@6.0.2): + ts-declaration-location@1.0.7(typescript@6.0.3): dependencies: picomatch: 4.0.4 - typescript: 6.0.2 + typescript: 6.0.3 ts-dedent@2.2.0: {} ts-pattern@5.9.0: {} - tsconfck@3.1.6(typescript@6.0.2): + tsconfck@3.1.6(typescript@6.0.3): optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 tsconfig-paths-webpack-plugin@4.2.0: dependencies: @@ -15653,7 +15894,7 @@ snapshots: dependencies: tagged-tag: 1.0.0 - typescript@6.0.2: {} + typescript@6.0.3: {} ufo@1.6.3: {} @@ -15802,9 +16043,9 @@ snapshots: uuid@13.0.0: {} - valibot@1.3.1(typescript@6.0.2): + valibot@1.3.1(typescript@6.0.3): optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 validate-npm-package-license@3.0.4: dependencies: @@ -15826,20 +16067,20 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.2): + vinext@0.0.41(@mdx-js/rollup@3.1.1)(@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)))(@vitejs/plugin-rsc@0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(typescript@6.0.3): dependencies: - '@unpic/react': 1.0.2(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@unpic/react': 1.0.2(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@vercel/og': 0.8.6 - '@vitejs/plugin-react': 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)) + '@vitejs/plugin-react': 6.0.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)) magic-string: 0.30.21 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' vite-plugin-commonjs: 0.10.4 - vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) + vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3) optionalDependencies: '@mdx-js/rollup': 3.1.1 - '@vitejs/plugin-rsc': 0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + '@vitejs/plugin-rsc': 0.5.24(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) react-server-dom-webpack: 19.2.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) transitivePeerDependencies: - next @@ -15859,9 +16100,9 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 - vite-plugin-inspect@12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0): + vite-plugin-inspect@12.0.0-beta.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3)(ws@8.20.0): dependencies: - '@vitejs/devtools-kit': 0.1.11(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2)(ws@8.20.0) + '@vitejs/devtools-kit': 0.1.11(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3)(ws@8.20.0) ansis: 4.2.0 error-stack-parser-es: 1.0.5 obug: 2.1.1 @@ -15870,31 +16111,31 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' transitivePeerDependencies: - typescript - ws - vite-plugin-storybook-nextjs@3.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(next@16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.2): + vite-plugin-storybook-nextjs@3.2.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(next@16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(storybook@10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3): dependencies: '@next/env': 16.0.0 image-size: 2.0.2 magic-string: 0.30.21 module-alias: 2.3.4 - next: 16.2.3(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + next: 16.2.4(@babel/core@7.29.0)(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' - vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' + vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3) transitivePeerDependencies: - supports-color - typescript - vite-plus@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3): + vite-plus@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3): dependencies: '@oxc-project/types': 0.126.0 - '@voidzero-dev/vite-plus-core': 0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) - '@voidzero-dev/vite-plus-test': 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) + '@voidzero-dev/vite-plus-core': 0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) + '@voidzero-dev/vite-plus-test': 0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3) oxfmt: 0.45.0 oxlint: 1.60.0(oxlint-tsgolint@0.21.1) oxlint-tsgolint: 0.21.1 @@ -15937,36 +16178,36 @@ snapshots: - vite - yaml - vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2): + vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3): dependencies: debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 - tsconfck: 3.1.6(typescript@6.0.2) + tsconfck: 3.1.6(typescript@6.0.3) optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(typescript@6.0.2): + vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(typescript@6.0.3): dependencies: debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 - tsconfck: 3.1.6(typescript@6.0.2) - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + tsconfck: 3.1.6(typescript@6.0.3) + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' transitivePeerDependencies: - supports-color - typescript - vitefu@1.1.3(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)): + vitefu@1.1.3(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)): optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' - vitest-browser-react@2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3): + vitest-browser-react@2.2.0(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3): dependencies: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) @@ -16000,11 +16241,11 @@ snapshots: - vite - yaml - vitest-canvas-mock@1.1.4(@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)): + vitest-canvas-mock@1.1.4(@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.19(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5(@types/node@25.6.0)(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(@voidzero-dev/vite-plus-core@0.1.19(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(tsx@4.21.0)(typescript@6.0.3)(yaml@2.8.3)' void-elements@3.1.0: {} @@ -16025,10 +16266,10 @@ snapshots: vscode-uri@3.1.0: {} - vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1)): + vue-eslint-parser@10.4.0(eslint@10.2.1(jiti@2.6.1)): dependencies: debug: 4.4.3(supports-color@8.1.1) - eslint: 10.2.0(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7a81789267..0d78fed290 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -47,12 +47,12 @@ overrides: yaml@>=2.0.0 <2.8.3: 2.8.3 yauzl@<3.2.1: 3.2.1 catalog: - '@amplitude/analytics-browser': 2.39.0 - '@amplitude/plugin-session-replay-browser': 1.27.7 + '@amplitude/analytics-browser': 2.41.0 + '@amplitude/plugin-session-replay-browser': 1.27.10 '@antfu/eslint-config': 8.2.0 '@base-ui/react': 1.4.1 '@chromatic-com/storybook': 5.1.2 - '@cucumber/cucumber': 12.8.0 + '@cucumber/cucumber': 12.8.1 '@egoist/tailwindcss-icons': 1.9.2 '@emoji-mart/data': 1.2.1 '@eslint-react/eslint-plugin': 3.0.0 @@ -75,8 +75,8 @@ catalog: '@mdx-js/react': 3.1.1 '@mdx-js/rollup': 3.1.1 '@monaco-editor/react': 4.7.0 - '@next/eslint-plugin-next': 16.2.3 - '@next/mdx': 16.2.3 + '@next/eslint-plugin-next': 16.2.4 + '@next/mdx': 16.2.4 '@orpc/client': 1.13.14 '@orpc/contract': 1.13.14 '@orpc/openapi-client': 1.13.14 @@ -84,7 +84,7 @@ catalog: '@playwright/test': 1.59.1 '@remixicon/react': 4.9.0 '@rgrove/parse-xml': 4.2.0 - '@sentry/react': 10.48.0 + '@sentry/react': 10.49.0 '@storybook/addon-docs': 10.3.5 '@storybook/addon-links': 10.3.5 '@storybook/addon-onboarding': 10.3.5 @@ -95,23 +95,23 @@ catalog: '@streamdown/math': 1.0.2 '@svgdotjs/svg.js': 3.2.5 '@t3-oss/env-nextjs': 0.13.11 - '@tailwindcss/postcss': 4.2.2 + '@tailwindcss/postcss': 4.2.4 '@tailwindcss/typography': 0.5.19 - '@tailwindcss/vite': 4.2.2 - '@tanstack/eslint-plugin-query': 5.99.0 + '@tailwindcss/vite': 4.2.4 + '@tanstack/eslint-plugin-query': 5.99.2 '@tanstack/react-devtools': 0.10.2 - '@tanstack/react-form': 1.29.0 - '@tanstack/react-form-devtools': 0.2.21 - '@tanstack/react-query': 5.99.0 - '@tanstack/react-query-devtools': 5.99.0 - '@tanstack/react-virtual': 3.13.23 + '@tanstack/react-form': 1.29.1 + '@tanstack/react-form-devtools': 0.2.22 + '@tanstack/react-query': 5.99.2 + '@tanstack/react-query-devtools': 5.99.2 + '@tanstack/react-virtual': 3.13.24 '@testing-library/dom': 10.4.1 '@testing-library/jest-dom': 6.9.1 '@testing-library/react': 16.3.2 '@testing-library/user-event': 14.6.1 - '@tsslint/cli': 3.0.3 - '@tsslint/compat-eslint': 3.0.3 - '@tsslint/config': 3.0.3 + '@tsslint/cli': 3.0.4 + '@tsslint/compat-eslint': 3.0.4 + '@tsslint/config': 3.0.4 '@types/js-cookie': 3.0.6 '@types/js-yaml': 4.0.9 '@types/negotiator': 0.6.4 @@ -120,12 +120,12 @@ catalog: '@types/react': 19.2.14 '@types/react-dom': 19.2.3 '@types/sortablejs': 1.15.9 - '@typescript-eslint/eslint-plugin': 8.58.2 - '@typescript-eslint/parser': 8.58.2 - '@typescript/native-preview': 7.0.0-dev.20260413.1 + '@typescript-eslint/eslint-plugin': 8.59.0 + '@typescript-eslint/parser': 8.59.0 + '@typescript/native-preview': 7.0.0-dev.20260422.1 '@vitejs/plugin-react': 6.0.1 '@vitejs/plugin-rsc': 0.5.24 - '@vitest/coverage-v8': 4.1.4 + '@vitest/coverage-v8': 4.1.5 abcjs: 6.6.2 agentation: 3.0.2 ahooks: 3.9.7 @@ -138,22 +138,22 @@ catalog: cron-parser: 5.5.0 dayjs: 1.11.20 decimal.js: 10.6.0 - dompurify: 3.4.0 + dompurify: 3.4.1 echarts: 6.0.0 echarts-for-react: 3.0.6 elkjs: 0.11.1 embla-carousel-autoplay: 8.6.0 embla-carousel-react: 8.6.0 emoji-mart: 5.6.0 - es-toolkit: 1.45.1 - eslint: 10.2.0 + es-toolkit: 1.46.0 + eslint: 10.2.1 eslint-markdown: 0.6.1 eslint-plugin-better-tailwindcss: 4.4.1 eslint-plugin-hyoban: 0.14.1 eslint-plugin-markdown-preferences: 0.41.1 eslint-plugin-no-barrel-files: 1.3.1 eslint-plugin-react-refresh: 0.5.2 - eslint-plugin-sonarjs: 4.0.2 + eslint-plugin-sonarjs: 4.0.3 eslint-plugin-storybook: 10.3.5 fast-deep-equal: 3.1.3 happy-dom: 20.9.0 @@ -161,7 +161,7 @@ catalog: hono: 4.12.14 html-entities: 2.6.0 html-to-image: 1.11.13 - i18next: 26.0.4 + i18next: 26.0.6 i18next-resources-to-backend: 1.2.1 iconify-import-svg: 0.2.0 immer: 11.1.4 @@ -171,21 +171,21 @@ catalog: js-yaml: 4.1.1 jsonschema: 1.5.0 katex: 0.16.45 - knip: 6.4.1 - ky: 2.0.0 + knip: 6.6.1 + ky: 2.0.2 lamejs: 1.2.1 lexical: 0.43.0 - loro-crdt: 1.10.8 + loro-crdt: 1.11.1 mermaid: 11.14.0 mime: 4.1.0 mitt: 3.0.1 negotiator: 1.0.0 - next: 16.2.3 + next: 16.2.4 next-themes: 0.4.6 nuqs: 2.8.9 pinyin-pro: 3.28.1 playwright: 1.59.1 - postcss: 8.5.9 + postcss: 8.5.10 qrcode.react: 4.2.0 qs: 6.15.1 react: 19.2.5 @@ -213,10 +213,10 @@ catalog: streamdown: 2.5.0 string-ts: 2.3.1 tailwind-merge: 3.5.0 - tailwindcss: 4.2.2 + tailwindcss: 4.2.4 tldts: 7.0.28 tsx: 4.21.0 - typescript: 6.0.2 + typescript: 6.0.3 uglify-js: 3.19.3 unist-util-visit: 5.1.0 use-context-selector: 2.0.0 diff --git a/sdks/nodejs-client/package.json b/sdks/nodejs-client/package.json index 28ebcb89c2..a8b9426e92 100644 --- a/sdks/nodejs-client/package.json +++ b/sdks/nodejs-client/package.json @@ -48,7 +48,7 @@ "build": "vp pack", "lint": "eslint", "lint:fix": "eslint --fix", - "type-check": "tsc", + "type-check": "tsgo", "test": "vp test", "test:coverage": "vp test --coverage", "publish:check": "./scripts/publish.sh --dry-run", @@ -60,6 +60,7 @@ "@types/node": "catalog:", "@typescript-eslint/eslint-plugin": "catalog:", "@typescript-eslint/parser": "catalog:", + "@typescript/native-preview": "catalog:", "@vitest/coverage-v8": "catalog:", "eslint": "catalog:", "typescript": "catalog:", diff --git a/web/docs/lint.md b/web/docs/lint.md index 4f1e819a03..ed38a7cf37 100644 --- a/web/docs/lint.md +++ b/web/docs/lint.md @@ -71,15 +71,15 @@ You should be able to see suggestions from TypeScript in your editor for all ope However, it can be useful to run the TypeScript 7 command-line (tsgo) to type check all files: ```sh -pnpm type-check:tsgo +pnpm type-check ``` -Prefer using `tsgo` for type checking as it is significantly faster than the standard TypeScript compiler. -Only fall back to `pnpm type-check` (which uses `tsc`) if you encounter unexpected results. +Type checking is powered by [`tsgo`] (the native TypeScript 7 compiler), which is significantly faster than `tsc`. [ESLint bulk suppressions blog post]: https://eslint.org/blog/2025/04/introducing-bulk-suppressions [ESLint multi-thread linting blog post]: https://eslint.org/blog/2025/08/multithread-linting [Overlay Migration Guide]: ./overlay-migration.md [TSSLint]: https://github.com/johnsoncodehk/tsslint +[`tsgo`]: https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-beta [no-leaked-conditional-rendering]: https://www.eslint-react.xyz/docs/rules/no-leaked-conditional-rendering [typed linting via typescript-eslint]: https://typescript-eslint.io/getting-started/typed-linting diff --git a/web/package.json b/web/package.json index 543181bd97..79005ac9f2 100644 --- a/web/package.json +++ b/web/package.json @@ -44,8 +44,7 @@ "test": "vp test", "test:coverage": "vp test --coverage", "test:watch": "vp test --watch", - "type-check": "tsc", - "type-check:tsgo": "tsgo", + "type-check": "tsgo", "uglify-embed": "node ./bin/uglify-embed" }, "dependencies": { diff --git a/web/scripts/refactor-component.js b/web/scripts/refactor-component.js index a054650ba3..58b440cd3a 100644 --- a/web/scripts/refactor-component.js +++ b/web/scripts/refactor-component.js @@ -156,7 +156,7 @@ Follow Dify project conventions: After refactoring, verify: - \`pnpm lint:fix\` passes -- \`pnpm type-check:tsgo\` passes +- \`pnpm type-check\` passes - Re-run \`pnpm refactor-component ${analysis.path}\` to confirm complexity < 50 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ From 8b1533438f1f0b098b6992f2c209c19d3200ed4e Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 22 Apr 2026 17:53:53 +0900 Subject: [PATCH 034/253] chore: update 3 api (#35481) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/controllers/console/workspace/endpoint.py | 201 ++++++++++++++---- .../console/workspace/test_endpoint.py | 171 +++++++++++++-- 2 files changed, 313 insertions(+), 59 deletions(-) diff --git a/api/controllers/console/workspace/endpoint.py b/api/controllers/console/workspace/endpoint.py index f45b72f390..d4be07382a 100644 --- a/api/controllers/console/workspace/endpoint.py +++ b/api/controllers/console/workspace/endpoint.py @@ -1,3 +1,11 @@ +"""Console workspace endpoint controllers. + +This module exposes workspace-scoped plugin endpoint management APIs. The +canonical write routes follow resource-oriented paths, while the historical +verb-based aliases stay available as deprecated resources so OpenAPI metadata +marks only the legacy paths as deprecated. +""" + from typing import Any from flask import request @@ -25,7 +33,12 @@ class EndpointIdPayload(BaseModel): endpoint_id: str -class EndpointUpdatePayload(EndpointIdPayload): +class EndpointUpdatePayload(BaseModel): + settings: dict[str, Any] + name: str = Field(min_length=1) + + +class LegacyEndpointUpdatePayload(EndpointIdPayload): settings: dict[str, Any] name: str = Field(min_length=1) @@ -76,6 +89,7 @@ register_schema_models( EndpointCreatePayload, EndpointIdPayload, EndpointUpdatePayload, + LegacyEndpointUpdatePayload, EndpointListQuery, EndpointListForPluginQuery, EndpointCreateResponse, @@ -88,8 +102,60 @@ register_schema_models( ) -@console_ns.route("/workspaces/current/endpoints/create") -class EndpointCreateApi(Resource): +def _create_endpoint() -> dict[str, bool]: + """Create a plugin endpoint for the current workspace.""" + user, tenant_id = current_account_with_tenant() + + args = EndpointCreatePayload.model_validate(console_ns.payload) + + try: + return { + "success": EndpointService.create_endpoint( + tenant_id=tenant_id, + user_id=user.id, + plugin_unique_identifier=args.plugin_unique_identifier, + name=args.name, + settings=args.settings, + ) + } + except PluginPermissionDeniedError as e: + raise ValueError(e.description) from e + + +def _update_endpoint(endpoint_id: str) -> dict[str, bool]: + """Update a plugin endpoint identified by the canonical path parameter.""" + user, tenant_id = current_account_with_tenant() + + args = EndpointUpdatePayload.model_validate(console_ns.payload) + + return { + "success": EndpointService.update_endpoint( + tenant_id=tenant_id, + user_id=user.id, + endpoint_id=endpoint_id, + name=args.name, + settings=args.settings, + ) + } + + +def _delete_endpoint(endpoint_id: str) -> dict[str, bool]: + """Delete a plugin endpoint identified by the canonical path parameter.""" + user, tenant_id = current_account_with_tenant() + + return { + "success": EndpointService.delete_endpoint( + tenant_id=tenant_id, + user_id=user.id, + endpoint_id=endpoint_id, + ) + } + + +@console_ns.route("/workspaces/current/endpoints") +class EndpointCollectionApi(Resource): + """Canonical collection resource for endpoint creation.""" + @console_ns.doc("create_endpoint") @console_ns.doc(description="Create a new plugin endpoint") @console_ns.expect(console_ns.models[EndpointCreatePayload.__name__]) @@ -104,22 +170,33 @@ class EndpointCreateApi(Resource): @is_admin_or_owner_required @account_initialization_required def post(self): - user, tenant_id = current_account_with_tenant() + return _create_endpoint() - args = EndpointCreatePayload.model_validate(console_ns.payload) - try: - return { - "success": EndpointService.create_endpoint( - tenant_id=tenant_id, - user_id=user.id, - plugin_unique_identifier=args.plugin_unique_identifier, - name=args.name, - settings=args.settings, - ) - } - except PluginPermissionDeniedError as e: - raise ValueError(e.description) from e +@console_ns.route("/workspaces/current/endpoints/create") +class DeprecatedEndpointCreateApi(Resource): + """Deprecated verb-based alias for endpoint creation.""" + + @console_ns.doc("create_endpoint_deprecated") + @console_ns.doc(deprecated=True) + @console_ns.doc( + description=( + "Deprecated legacy alias for creating a plugin endpoint. Use POST /workspaces/current/endpoints instead." + ) + ) + @console_ns.expect(console_ns.models[EndpointCreatePayload.__name__]) + @console_ns.response( + 200, + "Endpoint created successfully", + console_ns.models[EndpointCreateResponse.__name__], + ) + @console_ns.response(403, "Admin privileges required") + @setup_required + @login_required + @is_admin_or_owner_required + @account_initialization_required + def post(self): + return _create_endpoint() @console_ns.route("/workspaces/current/endpoints/list") @@ -190,10 +267,56 @@ class EndpointListForSinglePluginApi(Resource): ) -@console_ns.route("/workspaces/current/endpoints/delete") -class EndpointDeleteApi(Resource): +@console_ns.route("/workspaces/current/endpoints/") +class EndpointItemApi(Resource): + """Canonical item resource for endpoint updates and deletion.""" + @console_ns.doc("delete_endpoint") @console_ns.doc(description="Delete a plugin endpoint") + @console_ns.doc(params={"id": {"description": "Endpoint ID", "type": "string", "required": True}}) + @console_ns.response( + 200, + "Endpoint deleted successfully", + console_ns.models[EndpointDeleteResponse.__name__], + ) + @console_ns.response(403, "Admin privileges required") + @setup_required + @login_required + @is_admin_or_owner_required + @account_initialization_required + def delete(self, id: str): + return _delete_endpoint(endpoint_id=id) + + @console_ns.doc("update_endpoint") + @console_ns.doc(description="Update a plugin endpoint") + @console_ns.expect(console_ns.models[EndpointUpdatePayload.__name__]) + @console_ns.doc(params={"id": {"description": "Endpoint ID", "type": "string", "required": True}}) + @console_ns.response( + 200, + "Endpoint updated successfully", + console_ns.models[EndpointUpdateResponse.__name__], + ) + @console_ns.response(403, "Admin privileges required") + @setup_required + @login_required + @is_admin_or_owner_required + @account_initialization_required + def patch(self, id: str): + return _update_endpoint(endpoint_id=id) + + +@console_ns.route("/workspaces/current/endpoints/delete") +class DeprecatedEndpointDeleteApi(Resource): + """Deprecated verb-based alias for endpoint deletion.""" + + @console_ns.doc("delete_endpoint_deprecated") + @console_ns.doc(deprecated=True) + @console_ns.doc( + description=( + "Deprecated legacy alias for deleting a plugin endpoint. " + "Use DELETE /workspaces/current/endpoints/{id} instead." + ) + ) @console_ns.expect(console_ns.models[EndpointIdPayload.__name__]) @console_ns.response( 200, @@ -206,22 +329,23 @@ class EndpointDeleteApi(Resource): @is_admin_or_owner_required @account_initialization_required def post(self): - user, tenant_id = current_account_with_tenant() - args = EndpointIdPayload.model_validate(console_ns.payload) - - return { - "success": EndpointService.delete_endpoint( - tenant_id=tenant_id, user_id=user.id, endpoint_id=args.endpoint_id - ) - } + return _delete_endpoint(endpoint_id=args.endpoint_id) @console_ns.route("/workspaces/current/endpoints/update") -class EndpointUpdateApi(Resource): - @console_ns.doc("update_endpoint") - @console_ns.doc(description="Update a plugin endpoint") - @console_ns.expect(console_ns.models[EndpointUpdatePayload.__name__]) +class DeprecatedEndpointUpdateApi(Resource): + """Deprecated verb-based alias for endpoint updates.""" + + @console_ns.doc("update_endpoint_deprecated") + @console_ns.doc(deprecated=True) + @console_ns.doc( + description=( + "Deprecated legacy alias for updating a plugin endpoint. " + "Use PATCH /workspaces/current/endpoints/{id} instead." + ) + ) + @console_ns.expect(console_ns.models[LegacyEndpointUpdatePayload.__name__]) @console_ns.response( 200, "Endpoint updated successfully", @@ -233,19 +357,8 @@ class EndpointUpdateApi(Resource): @is_admin_or_owner_required @account_initialization_required def post(self): - user, tenant_id = current_account_with_tenant() - - args = EndpointUpdatePayload.model_validate(console_ns.payload) - - return { - "success": EndpointService.update_endpoint( - tenant_id=tenant_id, - user_id=user.id, - endpoint_id=args.endpoint_id, - name=args.name, - settings=args.settings, - ) - } + args = LegacyEndpointUpdatePayload.model_validate(console_ns.payload) + return _update_endpoint(endpoint_id=args.endpoint_id) @console_ns.route("/workspaces/current/endpoints/enable") diff --git a/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py b/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py index 51f76af172..0b3d7ef6d7 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py @@ -2,14 +2,17 @@ from unittest.mock import MagicMock, patch import pytest +from controllers.console import console_ns from controllers.console.workspace.endpoint import ( - EndpointCreateApi, - EndpointDeleteApi, + DeprecatedEndpointCreateApi, + DeprecatedEndpointDeleteApi, + DeprecatedEndpointUpdateApi, + EndpointCollectionApi, EndpointDisableApi, EndpointEnableApi, + EndpointItemApi, EndpointListApi, EndpointListForSinglePluginApi, - EndpointUpdateApi, ) from core.plugin.impl.exc import PluginPermissionDeniedError @@ -35,9 +38,9 @@ def patch_current_account(user_and_tenant): @pytest.mark.usefixtures("patch_current_account") -class TestEndpointCreateApi: +class TestEndpointCollectionApi: def test_create_success(self, app): - api = EndpointCreateApi() + api = EndpointCollectionApi() method = unwrap(api.post) payload = { @@ -55,7 +58,7 @@ class TestEndpointCreateApi: assert result["success"] is True def test_create_permission_denied(self, app): - api = EndpointCreateApi() + api = EndpointCollectionApi() method = unwrap(api.post) payload = { @@ -75,7 +78,7 @@ class TestEndpointCreateApi: method(api) def test_create_validation_error(self, app): - api = EndpointCreateApi() + api = EndpointCollectionApi() method = unwrap(api.post) payload = { @@ -91,6 +94,27 @@ class TestEndpointCreateApi: method(api) +@pytest.mark.usefixtures("patch_current_account") +class TestDeprecatedEndpointCreateApi: + def test_create_success(self, app): + api = DeprecatedEndpointCreateApi() + method = unwrap(api.post) + + payload = { + "plugin_unique_identifier": "plugin-1", + "name": "endpoint", + "settings": {"a": 1}, + } + + with ( + app.test_request_context("/", json=payload), + patch("controllers.console.workspace.endpoint.EndpointService.create_endpoint", return_value=True), + ): + result = method(api) + + assert result["success"] is True + + @pytest.mark.usefixtures("patch_current_account") class TestEndpointListApi: def test_list_success(self, app): @@ -146,9 +170,96 @@ class TestEndpointListForSinglePluginApi: @pytest.mark.usefixtures("patch_current_account") -class TestEndpointDeleteApi: +class TestEndpointItemApi: def test_delete_success(self, app): - api = EndpointDeleteApi() + api = EndpointItemApi() + method = unwrap(api.delete) + + with ( + app.test_request_context("/", method="DELETE"), + patch( + "controllers.console.workspace.endpoint.EndpointService.delete_endpoint", + return_value=True, + ) as mock_delete, + ): + result = method(api, "e1") + + assert result["success"] is True + mock_delete.assert_called_once_with(tenant_id="t1", user_id="u1", endpoint_id="e1") + + def test_delete_service_failure(self, app): + api = EndpointItemApi() + method = unwrap(api.delete) + + with ( + app.test_request_context("/", method="DELETE"), + patch("controllers.console.workspace.endpoint.EndpointService.delete_endpoint", return_value=False), + ): + result = method(api, "e1") + + assert result["success"] is False + + def test_update_success(self, app): + api = EndpointItemApi() + method = unwrap(api.patch) + + payload = { + "name": "new-name", + "settings": {"x": 1}, + } + + with ( + app.test_request_context("/", method="PATCH", json=payload), + patch( + "controllers.console.workspace.endpoint.EndpointService.update_endpoint", + return_value=True, + ) as mock_update, + ): + result = method(api, "e1") + + assert result["success"] is True + mock_update.assert_called_once_with( + tenant_id="t1", + user_id="u1", + endpoint_id="e1", + name="new-name", + settings={"x": 1}, + ) + + def test_update_validation_error(self, app): + api = EndpointItemApi() + method = unwrap(api.patch) + + payload = {"settings": {}} + + with ( + app.test_request_context("/", method="PATCH", json=payload), + ): + with pytest.raises(ValueError): + method(api, "e1") + + def test_update_service_failure(self, app): + api = EndpointItemApi() + method = unwrap(api.patch) + + payload = { + "name": "n", + "settings": {}, + } + + with ( + app.test_request_context("/", method="PATCH", json=payload), + patch("controllers.console.workspace.endpoint.EndpointService.update_endpoint", return_value=False), + ): + result = method(api, "e1") + + assert result["success"] is False + + +@pytest.mark.usefixtures("patch_current_account") +class TestDeprecatedEndpointDeleteApi: + def test_delete_success(self, app): + api = DeprecatedEndpointDeleteApi() method = unwrap(api.post) payload = {"endpoint_id": "e1"} @@ -162,7 +273,7 @@ class TestEndpointDeleteApi: assert result["success"] is True def test_delete_invalid_payload(self, app): - api = EndpointDeleteApi() + api = DeprecatedEndpointDeleteApi() method = unwrap(api.post) with ( @@ -172,7 +283,7 @@ class TestEndpointDeleteApi: method(api) def test_delete_service_failure(self, app): - api = EndpointDeleteApi() + api = DeprecatedEndpointDeleteApi() method = unwrap(api.post) payload = {"endpoint_id": "e1"} @@ -187,9 +298,9 @@ class TestEndpointDeleteApi: @pytest.mark.usefixtures("patch_current_account") -class TestEndpointUpdateApi: +class TestDeprecatedEndpointUpdateApi: def test_update_success(self, app): - api = EndpointUpdateApi() + api = DeprecatedEndpointUpdateApi() method = unwrap(api.post) payload = { @@ -207,7 +318,7 @@ class TestEndpointUpdateApi: assert result["success"] is True def test_update_validation_error(self, app): - api = EndpointUpdateApi() + api = DeprecatedEndpointUpdateApi() method = unwrap(api.post) payload = {"endpoint_id": "e1", "settings": {}} @@ -219,7 +330,7 @@ class TestEndpointUpdateApi: method(api) def test_update_service_failure(self, app): - api = EndpointUpdateApi() + api = DeprecatedEndpointUpdateApi() method = unwrap(api.post) payload = { @@ -237,6 +348,36 @@ class TestEndpointUpdateApi: assert result["success"] is False +class TestEndpointRouteMetadata: + def test_legacy_write_routes_are_marked_deprecated(self): + assert DeprecatedEndpointCreateApi.post.__apidoc__["deprecated"] is True + assert DeprecatedEndpointDeleteApi.post.__apidoc__["deprecated"] is True + assert DeprecatedEndpointUpdateApi.post.__apidoc__["deprecated"] is True + assert EndpointCollectionApi.post.__apidoc__.get("deprecated") is not True + assert EndpointItemApi.delete.__apidoc__.get("deprecated") is not True + assert EndpointItemApi.patch.__apidoc__.get("deprecated") is not True + + def test_canonical_and_legacy_write_routes_are_registered(self): + route_map = { + resource.__name__: urls + for resource, urls, _route_doc, _kwargs in console_ns.resources + if resource.__name__ + in { + "EndpointCollectionApi", + "EndpointItemApi", + "DeprecatedEndpointCreateApi", + "DeprecatedEndpointDeleteApi", + "DeprecatedEndpointUpdateApi", + } + } + + assert route_map["EndpointCollectionApi"] == ("/workspaces/current/endpoints",) + assert route_map["EndpointItemApi"] == ("/workspaces/current/endpoints/",) + assert route_map["DeprecatedEndpointCreateApi"] == ("/workspaces/current/endpoints/create",) + assert route_map["DeprecatedEndpointDeleteApi"] == ("/workspaces/current/endpoints/delete",) + assert route_map["DeprecatedEndpointUpdateApi"] == ("/workspaces/current/endpoints/update",) + + @pytest.mark.usefixtures("patch_current_account") class TestEndpointEnableApi: def test_enable_success(self, app): From 491061b8f427981c280d5da8329dcbf225988de3 Mon Sep 17 00:00:00 2001 From: Crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:54:23 +0800 Subject: [PATCH 035/253] fix(web): keep Add model dialog footer visible when form overflows (#35490) --- .../model-provider-page/model-modal/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx index a8f004f3a2..884a9cce70 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/index.tsx @@ -295,15 +295,15 @@ const ModelModal: FC = ({ -
+
{modalTitle} {modalDesc} {modalModel}
-
+
{ mode === ModelModalModeEnum.configCustomModel && ( = ({ ) }
-
+
{ (provider.help && (provider.help.title || provider.help.url)) ? ( @@ -410,7 +410,7 @@ const ModelModal: FC = ({
{ (mode === ModelModalModeEnum.configCustomModel || mode === ModelModalModeEnum.configProviderCredential) && ( -
+
{t('modelProvider.encrypted.front', { ns: 'common' })} From afec528f513cba7bcc4c929b5f1ab1980a2971d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Wed, 22 Apr 2026 16:55:16 +0800 Subject: [PATCH 036/253] feat: improve follow-up settings (#35442) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- README.md | 13 - api/.env.example | 16 -- .../manager.py | 31 ++- api/core/llm_generator/llm_generator.py | 103 +++++-- .../suggested_questions_after_answer.py | 12 +- api/core/llm_generator/prompts.py | 15 +- api/models/model.py | 24 +- api/services/message_service.py | 47 +++- .../test_additional_feature_managers.py | 32 +++ .../core/llm_generator/test_llm_generator.py | 100 +++++++ .../services/test_message_service.py | 125 +++++++++ docs/suggested-questions-configuration.md | 253 ------------------ .../follow-up-setting-modal.spec.tsx | 97 +++++++ .../__tests__/follow-up.spec.tsx | 89 +++++- .../follow-up-setting-modal.tsx | 241 +++++++++++++++++ .../features/new-feature-panel/follow-up.tsx | 93 ++++++- web/app/components/base/features/types.ts | 12 +- web/i18n/ar-TN/app-debug.json | 5 + web/i18n/de-DE/app-debug.json | 5 + web/i18n/en-US/app-debug.json | 9 + web/i18n/es-ES/app-debug.json | 5 + web/i18n/fa-IR/app-debug.json | 5 + web/i18n/fr-FR/app-debug.json | 5 + web/i18n/hi-IN/app-debug.json | 5 + web/i18n/id-ID/app-debug.json | 5 + web/i18n/it-IT/app-debug.json | 5 + web/i18n/ja-JP/app-debug.json | 5 + web/i18n/ko-KR/app-debug.json | 5 + web/i18n/nl-NL/app-debug.json | 5 + web/i18n/pl-PL/app-debug.json | 5 + web/i18n/pt-BR/app-debug.json | 5 + web/i18n/ro-RO/app-debug.json | 5 + web/i18n/ru-RU/app-debug.json | 5 + web/i18n/sl-SI/app-debug.json | 5 + web/i18n/th-TH/app-debug.json | 5 + web/i18n/tr-TR/app-debug.json | 5 + web/i18n/uk-UA/app-debug.json | 5 + web/i18n/vi-VN/app-debug.json | 5 + web/i18n/zh-Hans/app-debug.json | 9 + web/i18n/zh-Hant/app-debug.json | 5 + web/models/debug.ts | 7 +- web/types/app.ts | 2 + 42 files changed, 1086 insertions(+), 349 deletions(-) delete mode 100644 docs/suggested-questions-configuration.md create mode 100644 web/app/components/base/features/new-feature-panel/__tests__/follow-up-setting-modal.spec.tsx create mode 100644 web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx diff --git a/README.md b/README.md index d9848a6c78..c87472ace3 100644 --- a/README.md +++ b/README.md @@ -139,19 +139,6 @@ Star Dify on GitHub and be instantly notified of new releases. If you need to customize the configuration, please refer to the comments in our [.env.example](docker/.env.example) file and update the corresponding values in your `.env` file. Additionally, you might need to make adjustments to the `docker-compose.yaml` file itself, such as changing image versions, port mappings, or volume mounts, based on your specific deployment environment and requirements. After making any changes, please re-run `docker compose up -d`. You can find the full list of available environment variables [here](https://docs.dify.ai/getting-started/install-self-hosted/environments). -#### Customizing Suggested Questions - -You can now customize the "Suggested Questions After Answer" feature to better fit your use case. For example, to generate longer, more technical questions: - -```bash -# In your .env file -SUGGESTED_QUESTIONS_PROMPT='Please help me predict the five most likely technical follow-up questions a developer would ask. Focus on implementation details, best practices, and architecture considerations. Keep each question between 40-60 characters. Output must be JSON array: ["question1","question2","question3","question4","question5"]' -SUGGESTED_QUESTIONS_MAX_TOKENS=512 -SUGGESTED_QUESTIONS_TEMPERATURE=0.3 -``` - -See the [Suggested Questions Configuration Guide](docs/suggested-questions-configuration.md) for detailed examples and usage instructions. - ### Metrics Monitoring with Grafana Import the dashboard to Grafana, using Dify's PostgreSQL database as data source, to monitor metrics in granularity of apps, tenants, messages, and more. diff --git a/api/.env.example b/api/.env.example index 7455d4a0e9..6cfe0266c2 100644 --- a/api/.env.example +++ b/api/.env.example @@ -709,22 +709,6 @@ SWAGGER_UI_PATH=/swagger-ui.html # Set to false to export dataset IDs as plain text for easier cross-environment import DSL_EXPORT_ENCRYPT_DATASET_ID=true -# Suggested Questions After Answer Configuration -# These environment variables allow customization of the suggested questions feature -# -# Custom prompt for generating suggested questions (optional) -# If not set, uses the default prompt that generates 3 questions under 20 characters each -# Example: "Please help me predict the five most likely technical follow-up questions a developer would ask. Focus on implementation details, best practices, and architecture considerations. Keep each question between 40-60 characters. Output must be JSON array: [\"question1\",\"question2\",\"question3\",\"question4\",\"question5\"]" -# SUGGESTED_QUESTIONS_PROMPT= - -# Maximum number of tokens for suggested questions generation (default: 256) -# Adjust this value for longer questions or more questions -# SUGGESTED_QUESTIONS_MAX_TOKENS=256 - -# Temperature for suggested questions generation (default: 0.0) -# Higher values (0.5-1.0) produce more creative questions, lower values (0.0-0.3) produce more focused questions -# SUGGESTED_QUESTIONS_TEMPERATURE=0 - # Tenant isolated task queue configuration TENANT_ISOLATED_TASK_CONCURRENCY=1 diff --git a/api/core/app/app_config/features/suggested_questions_after_answer/manager.py b/api/core/app/app_config/features/suggested_questions_after_answer/manager.py index 2dddce349c..0c36992c77 100644 --- a/api/core/app/app_config/features/suggested_questions_after_answer/manager.py +++ b/api/core/app/app_config/features/suggested_questions_after_answer/manager.py @@ -1,5 +1,7 @@ from typing import Any +CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH = 1000 + class SuggestedQuestionsAfterAnswerConfigManager: @classmethod @@ -20,7 +22,11 @@ class SuggestedQuestionsAfterAnswerConfigManager: @classmethod def validate_and_set_defaults(cls, config: dict[str, Any]) -> tuple[dict[str, Any], list[str]]: """ - Validate and set defaults for suggested questions feature + Validate and set defaults for suggested questions feature. + + Optional fields: + - prompt: custom instruction prompt. + - model: provider/model configuration for suggested question generation. :param config: app model config args """ @@ -39,4 +45,27 @@ class SuggestedQuestionsAfterAnswerConfigManager: if not isinstance(config["suggested_questions_after_answer"]["enabled"], bool): raise ValueError("enabled in suggested_questions_after_answer must be of boolean type") + prompt = config["suggested_questions_after_answer"].get("prompt") + if prompt is not None and not isinstance(prompt, str): + raise ValueError("prompt in suggested_questions_after_answer must be of string type") + if isinstance(prompt, str) and len(prompt) > CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH: + raise ValueError( + f"prompt in suggested_questions_after_answer must be less than or equal to " + f"{CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH} characters" + ) + + if "model" in config["suggested_questions_after_answer"]: + model_config = config["suggested_questions_after_answer"]["model"] + if not isinstance(model_config, dict): + raise ValueError("model in suggested_questions_after_answer must be of object type") + + if "provider" not in model_config or not isinstance(model_config["provider"], str): + raise ValueError("provider in suggested_questions_after_answer.model must be of string type") + + if "name" not in model_config or not isinstance(model_config["name"], str): + raise ValueError("name in suggested_questions_after_answer.model must be of string type") + + if "completion_params" in model_config and not isinstance(model_config["completion_params"], dict): + raise ValueError("completion_params in suggested_questions_after_answer.model must be of object type") + return config, ["suggested_questions_after_answer"] diff --git a/api/core/llm_generator/llm_generator.py b/api/core/llm_generator/llm_generator.py index 348526b0ef..6454f4f0dc 100644 --- a/api/core/llm_generator/llm_generator.py +++ b/api/core/llm_generator/llm_generator.py @@ -2,7 +2,7 @@ import json import logging import re from collections.abc import Sequence -from typing import Any, Protocol, TypedDict, cast +from typing import Any, NotRequired, Protocol, TypedDict, cast import json_repair from sqlalchemy import select @@ -13,13 +13,13 @@ from core.llm_generator.output_parser.rule_config_generator import RuleConfigGen from core.llm_generator.output_parser.suggested_questions_after_answer import SuggestedQuestionsAfterAnswerOutputParser from core.llm_generator.prompts import ( CONVERSATION_TITLE_PROMPT, + DEFAULT_SUGGESTED_QUESTIONS_MAX_TOKENS, + DEFAULT_SUGGESTED_QUESTIONS_TEMPERATURE, GENERATOR_QA_PROMPT, JAVASCRIPT_CODE_GENERATOR_PROMPT_TEMPLATE, LLM_MODIFY_CODE_SYSTEM, LLM_MODIFY_PROMPT_SYSTEM, PYTHON_CODE_GENERATOR_PROMPT_TEMPLATE, - SUGGESTED_QUESTIONS_MAX_TOKENS, - SUGGESTED_QUESTIONS_TEMPERATURE, SYSTEM_STRUCTURED_OUTPUT_GENERATE, WORKFLOW_RULE_CONFIG_PROMPT_GENERATE_TEMPLATE, ) @@ -41,6 +41,36 @@ from models.workflow import Workflow logger = logging.getLogger(__name__) +class SuggestedQuestionsModelConfig(TypedDict): + provider: str + name: str + completion_params: NotRequired[dict[str, object]] + + +def _normalize_completion_params(completion_params: dict[str, object]) -> tuple[dict[str, object], list[str]]: + """ + Normalize raw completion params into invocation parameters and stop sequences. + + This mirrors the app-model access path by separating ``stop`` from provider + parameters before invocation, then drops non-positive token limits because + some plugin-backed models reject ``0`` after mapping ``max_tokens`` to their + provider-specific output-token field. + """ + normalized_parameters = dict(completion_params) + stop_value = normalized_parameters.pop("stop", []) + if isinstance(stop_value, list) and all(isinstance(item, str) for item in stop_value): + stop = stop_value + else: + stop = [] + + for token_limit_key in ("max_tokens", "max_output_tokens"): + token_limit = normalized_parameters.get(token_limit_key) + if isinstance(token_limit, int | float) and token_limit <= 0: + normalized_parameters.pop(token_limit_key, None) + + return normalized_parameters, stop + + class WorkflowServiceInterface(Protocol): def get_draft_workflow(self, app_model: App, workflow_id: str | None = None) -> Workflow | None: pass @@ -123,8 +153,15 @@ class LLMGenerator: return name @classmethod - def generate_suggested_questions_after_answer(cls, tenant_id: str, histories: str) -> Sequence[str]: - output_parser = SuggestedQuestionsAfterAnswerOutputParser() + def generate_suggested_questions_after_answer( + cls, + tenant_id: str, + histories: str, + *, + instruction_prompt: str | None = None, + model_config: object | None = None, + ) -> Sequence[str]: + output_parser = SuggestedQuestionsAfterAnswerOutputParser(instruction_prompt=instruction_prompt) format_instructions = output_parser.get_format_instructions() prompt_template = PromptTemplateParser(template="{{histories}}\n{{format_instructions}}\nquestions:\n") @@ -133,10 +170,36 @@ class LLMGenerator: try: model_manager = ModelManager.for_tenant(tenant_id=tenant_id) - model_instance = model_manager.get_default_model_instance( - tenant_id=tenant_id, - model_type=ModelType.LLM, - ) + configured_model = cast(dict[str, object], model_config) if isinstance(model_config, dict) else {} + provider = configured_model.get("provider") + model_name = configured_model.get("name") + use_configured_model = False + + if isinstance(provider, str) and provider and isinstance(model_name, str) and model_name: + try: + model_instance = model_manager.get_model_instance( + tenant_id=tenant_id, + model_type=ModelType.LLM, + provider=provider, + model=model_name, + ) + use_configured_model = True + except Exception: + logger.warning( + "Failed to use configured suggested-questions model %s/%s, fallback to default model", + provider, + model_name, + exc_info=True, + ) + model_instance = model_manager.get_default_model_instance( + tenant_id=tenant_id, + model_type=ModelType.LLM, + ) + else: + model_instance = model_manager.get_default_model_instance( + tenant_id=tenant_id, + model_type=ModelType.LLM, + ) except InvokeAuthorizationError: return [] @@ -145,19 +208,29 @@ class LLMGenerator: questions: Sequence[str] = [] try: + configured_completion_params = configured_model.get("completion_params") + if use_configured_model and isinstance(configured_completion_params, dict): + model_parameters, stop = _normalize_completion_params(configured_completion_params) + elif use_configured_model: + model_parameters = {} + stop = [] + else: + # Default-model generation keeps the built-in suggested-questions tuning. + model_parameters = { + "max_tokens": DEFAULT_SUGGESTED_QUESTIONS_MAX_TOKENS, + "temperature": DEFAULT_SUGGESTED_QUESTIONS_TEMPERATURE, + } + stop = [] + response: LLMResult = model_instance.invoke_llm( prompt_messages=list(prompt_messages), - model_parameters={ - "max_tokens": SUGGESTED_QUESTIONS_MAX_TOKENS, - "temperature": SUGGESTED_QUESTIONS_TEMPERATURE, - }, + model_parameters=model_parameters, + stop=stop, stream=False, ) text_content = response.message.get_text_content() questions = output_parser.parse(text_content) if text_content else [] - except InvokeError: - questions = [] except Exception: logger.exception("Failed to generate suggested questions after answer") questions = [] diff --git a/api/core/llm_generator/output_parser/suggested_questions_after_answer.py b/api/core/llm_generator/output_parser/suggested_questions_after_answer.py index eec771181f..c030802c79 100644 --- a/api/core/llm_generator/output_parser/suggested_questions_after_answer.py +++ b/api/core/llm_generator/output_parser/suggested_questions_after_answer.py @@ -3,17 +3,21 @@ import logging import re from collections.abc import Sequence -from core.llm_generator.prompts import SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT +from core.llm_generator.prompts import DEFAULT_SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT logger = logging.getLogger(__name__) class SuggestedQuestionsAfterAnswerOutputParser: + def __init__(self, instruction_prompt: str | None = None) -> None: + self._instruction_prompt = instruction_prompt or DEFAULT_SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT + def get_format_instructions(self) -> str: - return SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT + return self._instruction_prompt def parse(self, text: str) -> Sequence[str]: - action_match = re.search(r"\[.*?\]", text.strip(), re.DOTALL) + stripped_text = text.strip() + action_match = re.search(r"\[.*?\]", stripped_text, re.DOTALL) questions: list[str] = [] if action_match is not None: try: @@ -23,4 +27,6 @@ class SuggestedQuestionsAfterAnswerOutputParser: else: if isinstance(json_obj, list): questions = [question for question in json_obj if isinstance(question, str)] + elif stripped_text: + logger.warning("Failed to find suggested questions payload array in text: %r", stripped_text[:200]) return questions diff --git a/api/core/llm_generator/prompts.py b/api/core/llm_generator/prompts.py index ee9a016c95..855a00c9cd 100644 --- a/api/core/llm_generator/prompts.py +++ b/api/core/llm_generator/prompts.py @@ -1,5 +1,4 @@ # Written by YORKI MINAKO🤡, Edited by Xiaoyi, Edited by yasu-oh -import os CONVERSATION_TITLE_PROMPT = """You are asked to generate a concise chat title by decomposing the user’s input into two parts: “Intention” and “Subject”. @@ -96,8 +95,8 @@ JAVASCRIPT_CODE_GENERATOR_PROMPT_TEMPLATE = ( ) -# Default prompt for suggested questions (can be overridden by environment variable) -_DEFAULT_SUGGESTED_QUESTIONS_AFTER_ANSWER_PROMPT = ( +# Default prompt and model parameters for suggested questions. +DEFAULT_SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT = ( "Please help me predict the three most likely questions that human would ask, " "and keep each question under 20 characters.\n" "MAKE SURE your output is the SAME language as the Assistant's latest response. " @@ -105,14 +104,8 @@ _DEFAULT_SUGGESTED_QUESTIONS_AFTER_ANSWER_PROMPT = ( '["question1","question2","question3"]\n' ) -# Environment variable override for suggested questions prompt -SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT = os.getenv( - "SUGGESTED_QUESTIONS_PROMPT", _DEFAULT_SUGGESTED_QUESTIONS_AFTER_ANSWER_PROMPT -) - -# Configurable LLM parameters for suggested questions (can be overridden by environment variables) -SUGGESTED_QUESTIONS_MAX_TOKENS = int(os.getenv("SUGGESTED_QUESTIONS_MAX_TOKENS", "256")) -SUGGESTED_QUESTIONS_TEMPERATURE = float(os.getenv("SUGGESTED_QUESTIONS_TEMPERATURE", "0")) +DEFAULT_SUGGESTED_QUESTIONS_MAX_TOKENS = 256 +DEFAULT_SUGGESTED_QUESTIONS_TEMPERATURE = 0.0 GENERATOR_QA_PROMPT = ( " The user will send a long text. Generate a Question and Answer pairs only using the knowledge" diff --git a/api/models/model.py b/api/models/model.py index a1117fc43a..a632735f39 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -91,6 +91,19 @@ class EnabledConfig(TypedDict): enabled: bool +class SuggestedQuestionsAfterAnswerModelConfig(TypedDict): + provider: str + name: str + mode: NotRequired[str] + completion_params: NotRequired[dict[str, Any]] + + +class SuggestedQuestionsAfterAnswerConfig(TypedDict): + enabled: bool + model: NotRequired[SuggestedQuestionsAfterAnswerModelConfig] + prompt: NotRequired[str] + + class EmbeddingModelInfo(TypedDict): embedding_provider_name: str embedding_model_name: str @@ -220,7 +233,7 @@ class ModelConfig(TypedDict): class AppModelConfigDict(TypedDict): opening_statement: str | None suggested_questions: list[str] - suggested_questions_after_answer: EnabledConfig + suggested_questions_after_answer: SuggestedQuestionsAfterAnswerConfig speech_to_text: EnabledConfig text_to_speech: EnabledConfig retriever_resource: EnabledConfig @@ -680,8 +693,13 @@ class AppModelConfig(TypeBase): return cast(EnabledConfig, json.loads(value) if value else {"enabled": default_enabled}) @property - def suggested_questions_after_answer_dict(self) -> EnabledConfig: - return self._get_enabled_config(self.suggested_questions_after_answer) + def suggested_questions_after_answer_dict(self) -> SuggestedQuestionsAfterAnswerConfig: + return cast( + SuggestedQuestionsAfterAnswerConfig, + json.loads(self.suggested_questions_after_answer) + if self.suggested_questions_after_answer + else {"enabled": False}, + ) @property def speech_to_text_dict(self) -> EnabledConfig: diff --git a/api/services/message_service.py b/api/services/message_service.py index 98f24dd6a6..8f5e028d4d 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -1,4 +1,6 @@ +import logging from collections.abc import Sequence +from typing import cast from pydantic import TypeAdapter from sqlalchemy import select @@ -17,7 +19,16 @@ from graphon.model_runtime.entities.model_entities import ModelType from libs.infinite_scroll_pagination import InfiniteScrollPagination from models import Account from models.enums import FeedbackFromSource, FeedbackRating -from models.model import App, AppMode, AppModelConfig, AppModelConfigDict, EndUser, Message, MessageFeedback +from models.model import ( + App, + AppMode, + AppModelConfig, + AppModelConfigDict, + EndUser, + Message, + MessageFeedback, + SuggestedQuestionsAfterAnswerConfig, +) from repositories.execution_extra_content_repository import ExecutionExtraContentRepository from repositories.sqlalchemy_execution_extra_content_repository import ( SQLAlchemyExecutionExtraContentRepository, @@ -32,6 +43,7 @@ from services.errors.message import ( from services.workflow_service import WorkflowService _app_model_config_adapter: TypeAdapter[AppModelConfigDict] = TypeAdapter(AppModelConfigDict) +logger = logging.getLogger(__name__) def _create_execution_extra_content_repository() -> ExecutionExtraContentRepository: @@ -252,6 +264,7 @@ class MessageService: ) model_manager = ModelManager.for_tenant(tenant_id=app_model.tenant_id) + suggested_questions_after_answer_config: SuggestedQuestionsAfterAnswerConfig = {"enabled": False} if app_model.mode == AppMode.ADVANCED_CHAT: workflow_service = WorkflowService() @@ -271,9 +284,11 @@ class MessageService: if not app_config.additional_features.suggested_questions_after_answer: raise SuggestedQuestionsAfterAnswerDisabledError() - model_instance = model_manager.get_default_model_instance( - tenant_id=app_model.tenant_id, model_type=ModelType.LLM - ) + suggested_questions_after_answer = workflow.features_dict.get("suggested_questions_after_answer") + if isinstance(suggested_questions_after_answer, dict): + suggested_questions_after_answer_config = cast( + SuggestedQuestionsAfterAnswerConfig, suggested_questions_after_answer + ) else: if not conversation.override_model_configs: app_model_config = db.session.scalar( @@ -293,16 +308,14 @@ class MessageService: if not app_model_config: raise ValueError("did not find app model config") - suggested_questions_after_answer = app_model_config.suggested_questions_after_answer_dict - if suggested_questions_after_answer.get("enabled", False) is False: + suggested_questions_after_answer_config = app_model_config.suggested_questions_after_answer_dict + if suggested_questions_after_answer_config.get("enabled", False) is False: raise SuggestedQuestionsAfterAnswerDisabledError() - model_instance = model_manager.get_model_instance( - tenant_id=app_model.tenant_id, - provider=app_model_config.model_dict["provider"], - model_type=ModelType.LLM, - model=app_model_config.model_dict["name"], - ) + model_instance = model_manager.get_default_model_instance( + tenant_id=app_model.tenant_id, + model_type=ModelType.LLM, + ) # get memory of conversation (read-only) memory = TokenBufferMemory(conversation=conversation, model_instance=model_instance) @@ -312,9 +325,17 @@ class MessageService: message_limit=3, ) + instruction_prompt = suggested_questions_after_answer_config.get("prompt") + if not isinstance(instruction_prompt, str) or not instruction_prompt.strip(): + instruction_prompt = None + + configured_model = suggested_questions_after_answer_config.get("model") with measure_time() as timer: questions_sequence = LLMGenerator.generate_suggested_questions_after_answer( - tenant_id=app_model.tenant_id, histories=histories + tenant_id=app_model.tenant_id, + histories=histories, + instruction_prompt=instruction_prompt, + model_config=configured_model, ) questions: list[str] = list(questions_sequence) diff --git a/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py b/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py index dd00c3defc..0a0ffe657c 100644 --- a/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py +++ b/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py @@ -77,6 +77,38 @@ class TestAdditionalFeatureManagers: SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( {"suggested_questions_after_answer": {"enabled": "yes"}} ) + with pytest.raises(ValueError): + SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( + {"suggested_questions_after_answer": {"enabled": True, "prompt": 123}} + ) + with pytest.raises(ValueError, match="must be less than or equal to 1000 characters"): + SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( + {"suggested_questions_after_answer": {"enabled": True, "prompt": "a" * 1001}} + ) + with pytest.raises(ValueError): + SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( + {"suggested_questions_after_answer": {"enabled": True, "model": "bad"}} + ) + with pytest.raises(ValueError): + SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( + {"suggested_questions_after_answer": {"enabled": True, "model": {"provider": "openai"}}} + ) + + validated_config, _ = SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( + { + "suggested_questions_after_answer": { + "enabled": True, + "prompt": "custom prompt", + "model": { + "provider": "openai", + "name": "gpt-4o-mini", + "completion_params": {"max_tokens": 1024}, + }, + } + } + ) + assert validated_config["suggested_questions_after_answer"]["prompt"] == "custom prompt" + assert validated_config["suggested_questions_after_answer"]["model"]["name"] == "gpt-4o-mini" assert ( SuggestedQuestionsAfterAnswerConfigManager.convert({"suggested_questions_after_answer": {"enabled": True}}) diff --git a/api/tests/unit_tests/core/llm_generator/test_llm_generator.py b/api/tests/unit_tests/core/llm_generator/test_llm_generator.py index 2716f4712c..3b64ce6b5c 100644 --- a/api/tests/unit_tests/core/llm_generator/test_llm_generator.py +++ b/api/tests/unit_tests/core/llm_generator/test_llm_generator.py @@ -6,7 +6,12 @@ import pytest from core.app.app_config.entities import ModelConfig from core.llm_generator.entities import RuleCodeGeneratePayload, RuleGeneratePayload, RuleStructuredOutputPayload from core.llm_generator.llm_generator import LLMGenerator +from core.llm_generator.prompts import ( + DEFAULT_SUGGESTED_QUESTIONS_MAX_TOKENS, + DEFAULT_SUGGESTED_QUESTIONS_TEMPERATURE, +) from graphon.model_runtime.entities.llm_entities import LLMMode, LLMResult +from graphon.model_runtime.entities.model_entities import ModelType from graphon.model_runtime.errors.invoke import InvokeAuthorizationError, InvokeError @@ -96,6 +101,10 @@ class TestLLMGenerator: questions = LLMGenerator.generate_suggested_questions_after_answer("tenant_id", "histories") assert len(questions) == 2 assert questions[0] == "Question 1?" + assert mock_model_instance.invoke_llm.call_args.kwargs["model_parameters"] == { + "max_tokens": DEFAULT_SUGGESTED_QUESTIONS_MAX_TOKENS, + "temperature": DEFAULT_SUGGESTED_QUESTIONS_TEMPERATURE, + } def test_generate_suggested_questions_after_answer_auth_error(self, mock_model_instance): with patch("core.llm_generator.llm_generator.ModelManager.for_tenant") as mock_manager: @@ -113,6 +122,97 @@ class TestLLMGenerator: questions = LLMGenerator.generate_suggested_questions_after_answer("tenant_id", "histories") assert questions == [] + @patch("core.llm_generator.llm_generator.ModelManager.for_tenant") + def test_generate_suggested_questions_after_answer_with_custom_model_and_prompt(self, mock_for_tenant): + custom_model_instance = MagicMock() + custom_response = MagicMock() + custom_response.message.get_text_content.return_value = '["Question 1?"]' + custom_model_instance.invoke_llm.return_value = custom_response + + mock_for_tenant.return_value.get_model_instance.return_value = custom_model_instance + + questions = LLMGenerator.generate_suggested_questions_after_answer( + "tenant_id", + "histories", + instruction_prompt="custom prompt", + model_config={ + "provider": "openai", + "name": "gpt-4o", + "completion_params": {"temperature": 0.2}, + }, + ) + + assert questions == ["Question 1?"] + mock_for_tenant.return_value.get_model_instance.assert_called_once_with( + tenant_id="tenant_id", + model_type=ModelType.LLM, + provider="openai", + model="gpt-4o", + ) + + invoke_kwargs = custom_model_instance.invoke_llm.call_args.kwargs + assert invoke_kwargs["model_parameters"] == {"temperature": 0.2} + assert invoke_kwargs["stop"] == [] + assert "custom prompt" in invoke_kwargs["prompt_messages"][0].content + + @patch("core.llm_generator.llm_generator.ModelManager.for_tenant") + def test_generate_suggested_questions_after_answer_fallback_to_default_model(self, mock_for_tenant): + default_model_instance = MagicMock() + default_response = MagicMock() + default_response.message.get_text_content.return_value = '["Question 1?"]' + default_model_instance.invoke_llm.return_value = default_response + + mock_for_tenant.return_value.get_model_instance.side_effect = ValueError("invalid configured model") + mock_for_tenant.return_value.get_default_model_instance.return_value = default_model_instance + + questions = LLMGenerator.generate_suggested_questions_after_answer( + "tenant_id", + "histories", + model_config={ + "provider": "openai", + "name": "not-found-model", + "completion_params": {"temperature": 0.2}, + }, + ) + + assert questions == ["Question 1?"] + mock_for_tenant.return_value.get_default_model_instance.assert_called_once_with( + tenant_id="tenant_id", + model_type=ModelType.LLM, + ) + assert default_model_instance.invoke_llm.call_args.kwargs["model_parameters"] == { + "max_tokens": DEFAULT_SUGGESTED_QUESTIONS_MAX_TOKENS, + "temperature": DEFAULT_SUGGESTED_QUESTIONS_TEMPERATURE, + } + assert default_model_instance.invoke_llm.call_args.kwargs["stop"] == [] + + @patch("core.llm_generator.llm_generator.ModelManager.for_tenant") + def test_generate_suggested_questions_after_answer_drops_non_positive_max_tokens(self, mock_for_tenant): + custom_model_instance = MagicMock() + custom_response = MagicMock() + custom_response.message.get_text_content.return_value = '["Question 1?"]' + custom_model_instance.invoke_llm.return_value = custom_response + mock_for_tenant.return_value.get_model_instance.return_value = custom_model_instance + + questions = LLMGenerator.generate_suggested_questions_after_answer( + "tenant_id", + "histories", + model_config={ + "provider": "openai", + "name": "gpt-4o", + "completion_params": { + "temperature": 0.2, + "max_tokens": 0, + "stop": ["END"], + }, + }, + ) + + assert questions == ["Question 1?"] + invoke_kwargs = custom_model_instance.invoke_llm.call_args.kwargs + assert invoke_kwargs["model_parameters"] == {"temperature": 0.2} + assert invoke_kwargs["stop"] == ["END"] + def test_generate_rule_config_no_variable_success(self, mock_model_instance, model_config_entity): payload = RuleGeneratePayload( instruction="test instruction", model_config=model_config_entity, no_variable=True diff --git a/api/tests/unit_tests/services/test_message_service.py b/api/tests/unit_tests/services/test_message_service.py index 969132cfd8..7adc15d63e 100644 --- a/api/tests/unit_tests/services/test_message_service.py +++ b/api/tests/unit_tests/services/test_message_service.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch import pytest +from graphon.model_runtime.entities.model_entities import ModelType from libs.infinite_scroll_pagination import InfiniteScrollPagination from models.enums import FeedbackFromSource, FeedbackRating from models.model import App, AppMode, EndUser, Message @@ -931,6 +932,130 @@ class TestMessageServiceSuggestedQuestions: assert result == ["Q1?"] mock_llm_gen.generate_suggested_questions_after_answer.assert_called_once() + @patch("services.message_service.db") + @patch("services.message_service.ModelManager.for_tenant") + @patch("services.message_service.TokenBufferMemory") + @patch("services.message_service.LLMGenerator") + @patch("services.message_service.TraceQueueManager") + @patch.object(MessageService, "get_message") + @patch("services.message_service.ConversationService") + def test_get_suggested_questions_chat_app_uses_frontend_model_and_prompt( + self, + mock_conversation_service, + mock_get_message, + mock_trace_manager, + mock_llm_gen, + mock_memory, + mock_model_manager, + mock_db, + factory, + ): + """Test suggested question generation uses frontend configured model and prompt.""" + from core.app.entities.app_invoke_entities import InvokeFrom + + app = factory.create_app_mock(mode=AppMode.CHAT.value) + app.tenant_id = "tenant-123" + user = factory.create_end_user_mock() + message = factory.create_message_mock() + mock_get_message.return_value = message + + conversation = MagicMock() + conversation.override_model_configs = None + mock_conversation_service.get_conversation.return_value = conversation + + app_model_config = MagicMock() + app_model_config.suggested_questions_after_answer_dict = { + "enabled": True, + "prompt": "custom prompt", + "model": { + "provider": "openai", + "name": "gpt-4o-mini", + "completion_params": {"max_tokens": 2048, "temperature": 0.1}, + }, + } + mock_db.session.scalar.return_value = app_model_config + + mock_memory.return_value.get_history_prompt_text.return_value = "histories" + mock_llm_gen.generate_suggested_questions_after_answer.return_value = ["Q1?"] + + result = MessageService.get_suggested_questions_after_answer( + app_model=app, + user=user, + message_id="msg-123", + invoke_from=InvokeFrom.WEB_APP, + ) + + assert result == ["Q1?"] + mock_model_manager.return_value.get_default_model_instance.assert_called_once_with( + tenant_id="tenant-123", + model_type=ModelType.LLM, + ) + mock_memory.assert_called_once_with( + conversation=conversation, + model_instance=mock_model_manager.return_value.get_default_model_instance.return_value, + ) + mock_llm_gen.generate_suggested_questions_after_answer.assert_called_once_with( + tenant_id="tenant-123", + histories="histories", + instruction_prompt="custom prompt", + model_config={ + "provider": "openai", + "name": "gpt-4o-mini", + "completion_params": {"max_tokens": 2048, "temperature": 0.1}, + }, + ) + + @patch("services.message_service.db") + @patch("services.message_service.ModelManager.for_tenant") + @patch("services.message_service.TokenBufferMemory") + @patch("services.message_service.LLMGenerator") + @patch("services.message_service.TraceQueueManager") + @patch.object(MessageService, "get_message") + @patch("services.message_service.ConversationService") + def test_get_suggested_questions_chat_app_invalid_frontend_model_fallback_to_default( + self, + mock_conversation_service, + mock_get_message, + mock_trace_manager, + mock_llm_gen, + mock_memory, + mock_model_manager, + mock_db, + factory, + ): + """Test invalid frontend configured model falls back to tenant default model.""" + app = factory.create_app_mock(mode=AppMode.CHAT.value) + app.tenant_id = "tenant-123" + user = factory.create_end_user_mock() + message = factory.create_message_mock() + mock_get_message.return_value = message + + conversation = MagicMock() + conversation.override_model_configs = None + mock_conversation_service.get_conversation.return_value = conversation + + app_model_config = MagicMock() + app_model_config.suggested_questions_after_answer_dict = { + "enabled": True, + "model": {"provider": "openai", "name": "invalid-model"}, + } + mock_db.session.scalar.return_value = app_model_config + + mock_model_manager.return_value.get_model_instance.side_effect = ValueError("invalid model") + mock_memory.return_value.get_history_prompt_text.return_value = "histories" + mock_llm_gen.generate_suggested_questions_after_answer.return_value = ["Q1?"] + + result = MessageService.get_suggested_questions_after_answer( + app_model=app, user=user, message_id="msg-123", invoke_from=MagicMock() + ) + + assert result == ["Q1?"] + mock_model_manager.return_value.get_default_model_instance.assert_called_once_with( + tenant_id="tenant-123", + model_type=ModelType.LLM, + ) + mock_model_manager.return_value.get_model_instance.assert_not_called() + # Test 30: get_suggested_questions_after_answer - Disabled Error @patch("services.message_service.WorkflowService") @patch("services.message_service.AdvancedChatAppConfigManager") diff --git a/docs/suggested-questions-configuration.md b/docs/suggested-questions-configuration.md deleted file mode 100644 index c726d3b157..0000000000 --- a/docs/suggested-questions-configuration.md +++ /dev/null @@ -1,253 +0,0 @@ -# Configurable Suggested Questions After Answer - -This document explains how to configure the "Suggested Questions After Answer" feature in Dify using environment variables. - -## Overview - -The suggested questions feature generates follow-up questions after each AI response to help users continue the conversation. By default, Dify generates 3 short questions (under 20 characters each), but you can customize this behavior to better fit your specific use case. - -## Environment Variables - -### `SUGGESTED_QUESTIONS_PROMPT` - -**Description**: Custom prompt template for generating suggested questions. - -**Default**: - -``` -Please help me predict the three most likely questions that human would ask, and keep each question under 20 characters. -MAKE SURE your output is the SAME language as the Assistant's latest response. -The output must be an array in JSON format following the specified schema: -["question1","question2","question3"] -``` - -**Usage Examples**: - -1. **Technical/Developer Questions (Your Use Case)**: - - ```bash - export SUGGESTED_QUESTIONS_PROMPT='Please help me predict the five most likely technical follow-up questions a developer would ask. Focus on implementation details, best practices, and architecture considerations. Keep each question between 40-60 characters. Output must be JSON array: ["question1","question2","question3","question4","question5"]' - ``` - -1. **Customer Support**: - - ```bash - export SUGGESTED_QUESTIONS_PROMPT='Generate 3 helpful follow-up questions that guide customers toward solving their own problems. Focus on troubleshooting steps and common issues. Keep questions under 30 characters. JSON format: ["q1","q2","q3"]' - ``` - -1. **Educational Content**: - - ```bash - export SUGGESTED_QUESTIONS_PROMPT='Create 4 thought-provoking questions that help students deeper understand the topic. Focus on concepts, relationships, and applications. Questions should be 25-40 characters. JSON: ["question1","question2","question3","question4"]' - ``` - -1. **Multilingual Support**: - - ```bash - export SUGGESTED_QUESTIONS_PROMPT='Generate exactly 3 follow-up questions in the same language as the conversation. Adapt question length appropriately for the language (Chinese: 10-15 chars, English: 20-30 chars, Arabic: 25-35 chars). Always output valid JSON array.' - ``` - -**Important Notes**: - -- The prompt must request JSON array output format -- Include language matching instructions for multilingual support -- Specify clear character limits or question count requirements -- Focus on your specific domain or use case - -### `SUGGESTED_QUESTIONS_MAX_TOKENS` - -**Description**: Maximum number of tokens for the LLM response. - -**Default**: `256` - -**Usage**: - -```bash -export SUGGESTED_QUESTIONS_MAX_TOKENS=512 # For longer questions or more questions -``` - -**Recommended Values**: - -- `256`: Default, good for 3-4 short questions -- `384`: Medium, good for 4-5 medium-length questions -- `512`: High, good for 5+ longer questions or complex prompts -- `1024`: Maximum, for very complex question generation - -### `SUGGESTED_QUESTIONS_TEMPERATURE` - -**Description**: Temperature parameter for LLM creativity. - -**Default**: `0.0` - -**Usage**: - -```bash -export SUGGESTED_QUESTIONS_TEMPERATURE=0.3 # Balanced creativity -``` - -**Recommended Values**: - -- `0.0-0.2`: Very focused, predictable questions (good for technical support) -- `0.3-0.5`: Balanced creativity and relevance (good for general use) -- `0.6-0.8`: More creative, diverse questions (good for brainstorming) -- `0.9-1.0`: Maximum creativity (good for educational exploration) - -## Configuration Examples - -### Example 1: Developer Documentation Chatbot - -```bash -# .env file -SUGGESTED_QUESTIONS_PROMPT='Generate exactly 5 technical follow-up questions that developers would ask after reading code documentation. Focus on implementation details, edge cases, performance considerations, and best practices. Each question should be 40-60 characters long. Output as JSON array: ["question1","question2","question3","question4","question5"]' -SUGGESTED_QUESTIONS_MAX_TOKENS=512 -SUGGESTED_QUESTIONS_TEMPERATURE=0.3 -``` - -### Example 2: Customer Service Bot - -```bash -# .env file -SUGGESTED_QUESTIONS_PROMPT='Create 3 actionable follow-up questions that help customers resolve their own issues. Focus on common problems, troubleshooting steps, and product features. Keep questions simple and under 25 characters. JSON: ["q1","q2","q3"]' -SUGGESTED_QUESTIONS_MAX_TOKENS=256 -SUGGESTED_QUESTIONS_TEMPERATURE=0.1 -``` - -### Example 3: Educational Tutor - -```bash -# .env file -SUGGESTED_QUESTIONS_PROMPT='Generate 4 thought-provoking questions that help students deepen their understanding of the topic. Focus on relationships between concepts, practical applications, and critical thinking. Questions should be 30-45 characters. Output: ["question1","question2","question3","question4"]' -SUGGESTED_QUESTIONS_MAX_TOKENS=384 -SUGGESTED_QUESTIONS_TEMPERATURE=0.6 -``` - -## Implementation Details - -### How It Works - -1. **Environment Variable Loading**: The system checks for environment variables at startup -1. **Fallback to Defaults**: If no environment variables are set, original behavior is preserved -1. **Prompt Template**: The custom prompt is used as-is, allowing full control over question generation -1. **LLM Parameters**: Custom max_tokens and temperature are passed to the LLM API -1. **JSON Parsing**: The system expects JSON array output and parses it accordingly - -### File Changes - -The implementation modifies these files: - -- `api/core/llm_generator/prompts.py`: Environment variable support -- `api/core/llm_generator/llm_generator.py`: Custom LLM parameters -- `api/.env.example`: Documentation of new variables - -### Backward Compatibility - -- ✅ **Zero Breaking Changes**: Works exactly as before if no environment variables are set -- ✅ **Default Behavior Preserved**: Original prompt and parameters used as fallbacks -- ✅ **No Database Changes**: Pure environment variable configuration -- ✅ **No UI Changes Required**: Configuration happens at deployment level - -## Testing Your Configuration - -### Local Testing - -1. Set environment variables: - - ```bash - export SUGGESTED_QUESTIONS_PROMPT='Your test prompt...' - export SUGGESTED_QUESTIONS_MAX_TOKENS=300 - export SUGGESTED_QUESTIONS_TEMPERATURE=0.4 - ``` - -1. Start Dify API: - - ```bash - cd api - python -m flask run --host 0.0.0.0 --port=5001 --debug - ``` - -1. Test the feature in your chat application and verify the questions match your expectations. - -### Monitoring - -Monitor the following when testing: - -- **Question Quality**: Are questions relevant and helpful? -- **Language Matching**: Do questions match the conversation language? -- **JSON Format**: Is output properly formatted as JSON array? -- **Length Constraints**: Do questions follow your length requirements? -- **Response Time**: Are the custom parameters affecting performance? - -## Troubleshooting - -### Common Issues - -1. **Invalid JSON Output**: - - - **Problem**: LLM doesn't return valid JSON - - **Solution**: Make sure your prompt explicitly requests JSON array format - -1. **Questions Too Long/Short**: - - - **Problem**: Questions don't follow length constraints - - **Solution**: Be more specific about character limits in your prompt - -1. **Too Few/Many Questions**: - - - **Problem**: Wrong number of questions generated - - **Solution**: Clearly specify the exact number in your prompt - -1. **Language Mismatch**: - - - **Problem**: Questions in wrong language - - **Solution**: Include explicit language matching instructions in prompt - -1. **Performance Issues**: - - - **Problem**: Slow response times - - **Solution**: Reduce `SUGGESTED_QUESTIONS_MAX_TOKENS` or simplify prompt - -### Debug Logging - -To debug your configuration, you can temporarily add logging to see the actual prompt and parameters being used: - -```python -import logging -logger = logging.getLogger(__name__) - -# In llm_generator.py -logger.info(f"Suggested questions prompt: {prompt}") -logger.info(f"Max tokens: {SUGGESTED_QUESTIONS_MAX_TOKENS}") -logger.info(f"Temperature: {SUGGESTED_QUESTIONS_TEMPERATURE}") -``` - -## Migration Guide - -### From Default Configuration - -If you're currently using the default configuration and want to customize: - -1. **Assess Your Needs**: Determine what aspects need customization (question count, length, domain focus) -1. **Design Your Prompt**: Write a custom prompt that addresses your specific use case -1. **Choose Parameters**: Select appropriate max_tokens and temperature values -1. **Test Incrementally**: Start with small changes and test thoroughly -1. **Deploy Gradually**: Roll out to production after successful testing - -### Best Practices - -1. **Start Simple**: Begin with minimal changes to the default prompt -1. **Test Thoroughly**: Test with various conversation types and languages -1. **Monitor Performance**: Watch for impact on response times and costs -1. **Get User Feedback**: Collect feedback on question quality and relevance -1. **Iterate**: Refine your configuration based on real-world usage - -## Future Enhancements - -This environment variable approach provides immediate customization while maintaining backward compatibility. Future enhancements could include: - -1. **App-Level Configuration**: Different apps with different suggested question settings -1. **Dynamic Prompts**: Context-aware prompts based on conversation content -1. **Multi-Model Support**: Different models for different types of questions -1. **Analytics Dashboard**: Insights into question effectiveness and usage patterns -1. **A/B Testing**: Built-in testing of different prompt configurations - -For now, the environment variable approach offers a simple, reliable way to customize the suggested questions feature for your specific needs. diff --git a/web/app/components/base/features/new-feature-panel/__tests__/follow-up-setting-modal.spec.tsx b/web/app/components/base/features/new-feature-panel/__tests__/follow-up-setting-modal.spec.tsx new file mode 100644 index 0000000000..9437a19824 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/__tests__/follow-up-setting-modal.spec.tsx @@ -0,0 +1,97 @@ +import type { SuggestedQuestionsAfterAnswer } from '@/app/components/base/features/types' +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import FollowUpSettingModal from '../follow-up-setting-modal' + +vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ + useModelListAndDefaultModelAndCurrentProviderAndModel: () => ({ + defaultModel: { + provider: { + provider: 'openai', + }, + model: 'gpt-4o-mini', + }, + }), +})) + +vi.mock('@/app/components/header/account-setting/model-provider-page/model-parameter-modal', () => ({ + default: ({ provider, modelId }: { provider: string, modelId: string }) => ( +
{`${provider}:${modelId}`}
+ ), +})) + +const renderModal = (data: SuggestedQuestionsAfterAnswer = { enabled: true }) => { + const onSave = vi.fn() + const onCancel = vi.fn() + + render( + , + ) + + return { + onSave, + onCancel, + } +} + +describe('FollowUpSettingModal', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('Default Prompt', () => { + it('should show the system default prompt and save without a custom prompt when no custom prompt is configured', async () => { + const user = userEvent.setup() + const { onSave } = renderModal() + + expect(screen.getByText('appDebug.feature.suggestedQuestionsAfterAnswer.modal.defaultPromptOption')).toBeInTheDocument() + expect(screen.getByText(/Please predict the three most likely follow-up questions a user would ask/)).toBeInTheDocument() + + await user.click(screen.getByText(/common\.operation\.save/)) + + expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ + prompt: undefined, + model: expect.objectContaining({ + provider: 'openai', + name: 'gpt-4o-mini', + }), + })) + }) + }) + + describe('Custom Prompt', () => { + it('should enable custom prompt input and save the custom prompt when selected', async () => { + const user = userEvent.setup() + const { onSave } = renderModal() + + await user.click(screen.getByText('appDebug.feature.suggestedQuestionsAfterAnswer.modal.customPromptOption').closest('button')!) + + const textarea = screen.getByPlaceholderText('appDebug.feature.suggestedQuestionsAfterAnswer.modal.promptPlaceholder') + expect(textarea).toHaveAttribute('maxLength', '1000') + + fireEvent.change( + textarea, + { target: { value: 'Use a custom follow-up prompt.' } }, + ) + + await user.click(screen.getByText(/common\.operation\.save/)) + + expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ + prompt: 'Use a custom follow-up prompt.', + })) + }) + + it('should disable save when custom prompt is selected but empty', async () => { + const user = userEvent.setup() + renderModal() + + await user.click(screen.getByText('appDebug.feature.suggestedQuestionsAfterAnswer.modal.customPromptOption').closest('button')!) + + expect(screen.getByText(/common\.operation\.save/).closest('button')).toBeDisabled() + }) + }) +}) diff --git a/web/app/components/base/features/new-feature-panel/__tests__/follow-up.spec.tsx b/web/app/components/base/features/new-feature-panel/__tests__/follow-up.spec.tsx index 0e7c6aa558..323032249d 100644 --- a/web/app/components/base/features/new-feature-panel/__tests__/follow-up.spec.tsx +++ b/web/app/components/base/features/new-feature-panel/__tests__/follow-up.spec.tsx @@ -1,12 +1,55 @@ -import type { OnFeaturesChange } from '../../types' +import type { + OnFeaturesChange, + SuggestedQuestionsAfterAnswer, +} from '../../types' import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' import { FeaturesProvider } from '../../context' import FollowUp from '../follow-up' -const renderWithProvider = (props: { disabled?: boolean, onChange?: OnFeaturesChange } = {}) => { +vi.mock('../follow-up-setting-modal', () => ({ + default: ({ onSave, onCancel }: { onSave: (newState: unknown) => void, onCancel: () => void }) => ( +
+ + +
+ ), +})) + +const renderWithProvider = ( + props: { + disabled?: boolean + onChange?: OnFeaturesChange + suggested?: SuggestedQuestionsAfterAnswer + } = {}, +) => { return render( - + , ) @@ -45,4 +88,44 @@ describe('FollowUp', () => { expect(() => fireEvent.click(screen.getByRole('switch'))).not.toThrow() }) + + it('should render edit button when enabled and hovering', () => { + renderWithProvider({ + suggested: { + enabled: true, + }, + }) + + fireEvent.mouseEnter(screen.getByText(/feature\.suggestedQuestionsAfterAnswer\.title/).closest('[class]')!) + + expect(screen.getByText(/operation\.settings/)).toBeInTheDocument() + }) + + it('should open settings modal and save follow-up config', () => { + const onChange = vi.fn() + renderWithProvider({ + onChange, + suggested: { + enabled: true, + }, + }) + + fireEvent.mouseEnter(screen.getByText(/feature\.suggestedQuestionsAfterAnswer\.title/).closest('[class]')!) + fireEvent.click(screen.getByText(/operation\.settings/)) + + expect(screen.getByTestId('follow-up-setting-modal')).toBeInTheDocument() + + fireEvent.click(screen.getByText('save-settings')) + + expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ + suggested: expect.objectContaining({ + enabled: true, + prompt: 'test prompt', + model: expect.objectContaining({ + provider: 'openai', + name: 'gpt-4o-mini', + }), + }), + })) + }) }) diff --git a/web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx b/web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx new file mode 100644 index 0000000000..24e89c3517 --- /dev/null +++ b/web/app/components/base/features/new-feature-panel/follow-up-setting-modal.tsx @@ -0,0 +1,241 @@ +import type { SuggestedQuestionsAfterAnswer } from '@/app/components/base/features/types' +import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { + CompletionParams, + Model, + ModelModeType, +} from '@/types/app' +import { Button } from '@langgenius/dify-ui/button' +import { cn } from '@langgenius/dify-ui/cn' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' +import { produce } from 'immer' +import { useCallback, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Radio from '@/app/components/base/radio/ui' +import Textarea from '@/app/components/base/textarea' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' +import { ModelModeType as ModelModeTypeEnum } from '@/types/app' + +type FollowUpSettingModalProps = { + data: SuggestedQuestionsAfterAnswer + onSave: (newState: SuggestedQuestionsAfterAnswer) => void + onCancel: () => void +} + +const DEFAULT_COMPLETION_PARAMS: CompletionParams = { + temperature: 0.7, + max_tokens: 0, + top_p: 0, + echo: false, + stop: [], + presence_penalty: 0, + frequency_penalty: 0, +} + +const DEFAULT_FOLLOW_UP_PROMPT = `Please predict the three most likely follow-up questions a user would ask, keep each question under 20 characters, use the same language as the assistant's latest response, and output a JSON array like ["question1", "question2", "question3"].` +const CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH = 1000 + +const getInitialModel = (model?: Model): Model => ({ + provider: model?.provider || '', + name: model?.name || '', + mode: model?.mode || ModelModeTypeEnum.chat, + completion_params: { + ...DEFAULT_COMPLETION_PARAMS, + ...(model?.completion_params || {}), + }, +}) + +const PROMPT_MODE = { + default: 'default', + custom: 'custom', +} as const + +type PromptMode = typeof PROMPT_MODE[keyof typeof PROMPT_MODE] + +const FollowUpSettingModal = ({ + data, + onSave, + onCancel, +}: FollowUpSettingModalProps) => { + const { t } = useTranslation() + const [model, setModel] = useState(() => getInitialModel(data.model)) + const [prompt, setPrompt] = useState(data.prompt || '') + const [promptMode, setPromptMode] = useState( + data.prompt ? PROMPT_MODE.custom : PROMPT_MODE.default, + ) + const { defaultModel } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration) + const selectedModel = useMemo(() => { + if (model.provider && model.name) + return model + + if (!defaultModel) + return model + + return { + ...model, + provider: defaultModel.provider.provider, + name: defaultModel.model, + } + }, [defaultModel, model]) + + const handleModelChange = useCallback((newValue: { modelId: string, provider: string, mode?: string, features?: string[] }) => { + setModel(prev => ({ + ...prev, + provider: newValue.provider, + name: newValue.modelId, + mode: (newValue.mode as ModelModeType) || prev.mode || ModelModeTypeEnum.chat, + })) + }, []) + + const handleCompletionParamsChange = useCallback((newParams: FormValue) => { + setModel({ + ...selectedModel, + completion_params: { + ...DEFAULT_COMPLETION_PARAMS, + ...(newParams as Partial), + }, + }) + }, [selectedModel]) + + const handleSave = useCallback(() => { + const trimmedPrompt = prompt.trim() + const nextFollowUpState = produce(data, (draft) => { + if (selectedModel.provider && selectedModel.name) + draft.model = selectedModel + else + draft.model = undefined + + draft.prompt = promptMode === PROMPT_MODE.custom + ? (trimmedPrompt || undefined) + : undefined + }) + onSave(nextFollowUpState) + }, [data, onSave, prompt, promptMode, selectedModel]) + + const isCustomPromptInvalid = promptMode === PROMPT_MODE.custom && !prompt.trim() + + return ( + { + if (!open) + onCancel() + }} + > + + + + {t('feature.suggestedQuestionsAfterAnswer.modal.title', { ns: 'appDebug' })} + +
+
+
+ {t('feature.suggestedQuestionsAfterAnswer.modal.modelLabel', { ns: 'appDebug' })} +
+ +
+
+
+ {t('feature.suggestedQuestionsAfterAnswer.modal.promptLabel', { ns: 'appDebug' })} +
+
+ + - -
- + {latestParams.length > 0 && ( +
+
+
{t('mcp.server.modal.parameters', { ns: 'tools' })}
+ +
+
{t('mcp.server.modal.parametersTip', { ns: 'tools' })}
+
+ {latestParams.map((paramItem) => { + if (!paramItem.variable) + return null + + const { variable } = paramItem + + return ( + handleParamChange(variable, value)} + /> + ) + })} +
+
+ )} +
+
+ + +
+ +
) } diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 165535127d..79179ae3dc 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -4,17 +4,15 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { ToolWithProvider } from '@/app/components/workflow/types' import type { AppIconType } from '@/types/app' import { Button } from '@langgenius/dify-ui/button' -import { cn } from '@langgenius/dify-ui/cn' +import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' import { toast } from '@langgenius/dify-ui/toast' import { RiCloseLine, RiEditLine } from '@remixicon/react' import { useHover } from 'ahooks' -import { noop } from 'es-toolkit/function' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import AppIconPicker from '@/app/components/base/app-icon-picker' import { Mcp } from '@/app/components/base/icons/src/vender/other' import Input from '@/app/components/base/input' -import Modal from '@/app/components/base/modal' import TabSlider from '@/app/components/base/tab-slider' import { MCPAuthMethod } from '@/app/components/tools/types' import { shouldUseMcpIconForAppIcon } from '@/utils/mcp' @@ -281,18 +279,16 @@ const MCPModal: FC = ({ const formKey = data?.id ?? 'create' return ( - - - + + + + + ) } diff --git a/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx b/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx index 241cd7d762..684c700648 100644 --- a/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx +++ b/web/app/components/workflow/__tests__/update-dsl-modal.spec.tsx @@ -126,6 +126,15 @@ describe('UpdateDSLModal', () => { expect(defaultProps.onBackup).toHaveBeenCalledTimes(1) }) + it('should call cancel handler when the import dialog requests close', () => { + const onCancel = vi.fn() + renderModal({ ...defaultProps, onCancel }) + + fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' }) + + expect(onCancel).toHaveBeenCalledTimes(1) + }) + it('should import a valid file and emit workflow update payload', async () => { renderModal() @@ -228,6 +237,32 @@ describe('UpdateDSLModal', () => { }) }) + it('should close the pending modal when dialog requests close', async () => { + mockImportDSL.mockResolvedValue({ + id: 'import-8', + status: DSLImportStatus.PENDING, + imported_dsl_version: '1.0.0', + current_dsl_version: '2.0.0', + }) + + renderModal() + + fireEvent.change(screen.getByTestId('dsl-file-input'), { + target: { files: [new File(['workflow'], 'workflow.yml', { type: 'text/yaml' })] }, + }) + fireEvent.click(screen.getByRole('button', { name: 'workflow.common.overwriteAndImport' })) + + await waitFor(() => { + expect(screen.getByRole('button', { name: 'app.newApp.Confirm' })).toBeInTheDocument() + }) + + fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' }) + + await waitFor(() => { + expect(screen.queryByRole('button', { name: 'app.newApp.Confirm' })).not.toBeInTheDocument() + }) + }) + it('should show an error when the selected file content is invalid for the current app mode', async () => { class InvalidDSLFileReader extends MockFileReader { override readAsText(_file: Blob) { diff --git a/web/app/components/workflow/header/online-users.tsx b/web/app/components/workflow/header/online-users.tsx index 17e1de3feb..93e9d1fa85 100644 --- a/web/app/components/workflow/header/online-users.tsx +++ b/web/app/components/workflow/header/online-users.tsx @@ -189,7 +189,6 @@ const OnlineUsers = () => { placement="bottom-start" sideOffset={8} alignOffset={-48} - className="z-[9999]" popupClassName={cn( 'mt-1.5 flex max-h-[200px] w-[240px] flex-col overflow-y-auto', 'rounded-xl border-[0.5px] border-components-panel-border', diff --git a/web/app/components/workflow/nodes/http/components/authorization/index.tsx b/web/app/components/workflow/nodes/http/components/authorization/index.tsx index b72e52911d..684f943d5f 100644 --- a/web/app/components/workflow/nodes/http/components/authorization/index.tsx +++ b/web/app/components/workflow/nodes/http/components/authorization/index.tsx @@ -4,12 +4,12 @@ import type { Authorization as AuthorizationPayloadType } from '../../types' import type { Var } from '@/app/components/workflow/types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' import { produce } from 'immer' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import BaseInput from '@/app/components/base/input' -import Modal from '@/app/components/base/modal' import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import { VarType } from '@/app/components/workflow/types' @@ -115,70 +115,78 @@ const Authorization: FC = ({ onHide() }, [tempPayload, onChange, onHide]) return ( - { + if (!open) + onHide() + }} > -
-
- - - + + + {t(`${i18nPrefix}.authorization`, { ns: 'workflow' })} + - {tempPayload.type === AuthorizationType.apiKey && ( - <> - - - - {tempPayload.config?.type === APIType.custom && ( - - +
+ + + + + {tempPayload.type === AuthorizationType.apiKey && ( + <> + + - )} + {tempPayload.config?.type === APIType.custom && ( + + + + )} - -
- -
-
- - )} + +
+ +
+
+ + )} +
+
+ + +
-
- - -
-
-
+ +
) } export default React.memo(Authorization) diff --git a/web/app/components/workflow/nodes/http/components/curl-panel.tsx b/web/app/components/workflow/nodes/http/components/curl-panel.tsx index 8ba5fb36a9..87dc2a3427 100644 --- a/web/app/components/workflow/nodes/http/components/curl-panel.tsx +++ b/web/app/components/workflow/nodes/http/components/curl-panel.tsx @@ -2,11 +2,11 @@ import type { FC } from 'react' import type { HttpNodeType } from '../types' import { Button } from '@langgenius/dify-ui/button' +import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' import { toast } from '@langgenius/dify-ui/toast' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import Modal from '@/app/components/base/modal' import Textarea from '@/app/components/base/textarea' import { useNodesInteractions } from '@/app/components/workflow/hooks' import { parseCurl } from './curl-parser' @@ -42,28 +42,35 @@ const CurlPanel: FC = ({ nodeId, isShow, onHide, handleCurlImport }) => { }, [onHide, nodeId, inputString, handleNodeSelect, handleCurlImport]) return ( - { + if (!open) + onHide() + }} > -
-
- + + {latestParams.length > 0 && ( +
+
+
{t('mcp.server.modal.parameters', { ns: 'tools' })}
+ +
+
{t('mcp.server.modal.parametersTip', { ns: 'tools' })}
+
+ {latestParams.map((paramItem) => { + if (!paramItem.variable) + return null + + const { variable } = paramItem + + return ( + handleParamChange(variable, value)} + /> + ) + })} +
+
+ )} - {latestParams.length > 0 && ( -
-
-
{t('mcp.server.modal.parameters', { ns: 'tools' })}
- -
-
{t('mcp.server.modal.parametersTip', { ns: 'tools' })}
-
- {latestParams.map((paramItem) => { - if (!paramItem.variable) - return null - - const { variable } = paramItem - - return ( - handleParamChange(variable, value)} - /> - ) - })} -
-
- )} -
- +
+
diff --git a/web/app/components/tools/mcp/mcp-server-param-item.tsx b/web/app/components/tools/mcp/mcp-server-param-item.tsx index db27cfdf98..316bbca556 100644 --- a/web/app/components/tools/mcp/mcp-server-param-item.tsx +++ b/web/app/components/tools/mcp/mcp-server-param-item.tsx @@ -17,12 +17,12 @@ const MCPServerParamItem = ({ const { t } = useTranslation() return ( -
-
-
{data.label}
+
+
+
{data.label}
·
-
{data.variable}
-
{data.type}
+
{data.variable}
+
{data.type}